Skip to content

Commit

Permalink
Merge pull request #998 from contour-terminal/improvement/modal-mode-…
Browse files Browse the repository at this point in the history
…again

Refactoring modal input mode handler
  • Loading branch information
christianparpart authored Jan 22, 2023
2 parents 7f7ddfd + eff4061 commit 64575c2
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 326 deletions.
1 change: 1 addition & 0 deletions metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
<li>Improvements to vi-like normal mode: Add `%` motion to jump to matching symbol pairs.</li>
<li>Improvements to vi-like normal mode: Add `M` motion to jump to middle screen line (same column).</li>
<li>Improvements to vi-like normal mode: Add `P` to paste the clipboard with newlines stripped.</li>
<li>Improvements to vi-like normal mode: Add `SP` and `BS` to move left/right.</li>
</ul>
</description>
</release>
Expand Down
2 changes: 2 additions & 0 deletions src/crispy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(crispy_SOURCES
LRUCache.h
StrongLRUCache.h
StackTrace.cpp StackTrace.h
TrieMap.h
algorithm.h
assert.h
base64.h
Expand Down Expand Up @@ -122,6 +123,7 @@ if(CRISPY_TESTING)
LRUCache_test.cpp
StrongLRUCache_test.cpp
StrongLRUHashtable_test.cpp
TrieMap_test.cpp
base64_test.cpp
indexed_test.cpp
compose_test.cpp
Expand Down
115 changes: 115 additions & 0 deletions src/crispy/TrieMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* This file is part of the "libterminal" project
* Copyright (c) 2019-2020 Christian Parpart <christian@parpart.family>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once

#include <array>
#include <cassert>
#include <memory>
#include <optional>
#include <variant>

namespace crispy
{

// clang-format off
struct NoMatch {};
struct PartialMatch {};
template <typename T> struct ExactMatch { T const& value; };
template <typename T> using TrieMatch = std::variant<ExactMatch<T>, PartialMatch, NoMatch>;
// clang-format on

namespace detail
{
template <typename Value>
struct TrieNode
{
std::array<std::unique_ptr<TrieNode<Value>>, 256> children;
std::optional<Value> value;
};
} // namespace detail

/// General purpose Trie map data structure,
///
/// While this is a general purpose Trie data structure,
/// I only implemented as much as was needed to fit the purpose.
template <typename Key, typename Value>
class TrieMap
{
public:
void insert(Key const& key, Value value);
void clear();

[[nodiscard]] size_t size() const noexcept { return _size; }

[[nodiscard]] TrieMatch<Value> search(Key const& key) const noexcept;
[[nodiscard]] bool contains(Key const& key) const noexcept;

private:
detail::TrieNode<Value> _root;
size_t _size = 0;
};

template <typename Key, typename Value>
void TrieMap<Key, Value>::clear()
{
for (std::unique_ptr<detail::TrieNode<Value>>& childNode: _root.children)
childNode.reset();
_size = 0;
}

template <typename Key, typename Value>
void TrieMap<Key, Value>::insert(Key const& key, Value value)
{
detail::TrieNode<Value>* currentNode = &_root;
for (auto const element: key)
{
auto const childIndex = static_cast<uint8_t>(element);
if (!currentNode->children[childIndex])
currentNode->children[childIndex] = std::make_unique<detail::TrieNode<Value>>();
currentNode = currentNode->children[childIndex].get();
}

assert(!currentNode->value.has_value());

if (!currentNode->value.has_value())
++_size;

currentNode->value = std::move(value);
}

template <typename Key, typename Value>
TrieMatch<Value> TrieMap<Key, Value>::search(Key const& key) const noexcept
{
detail::TrieNode<Value> const* currentNode = &_root;
for (auto const element: key)
{
auto const childIndex = static_cast<uint8_t>(element);
if (!currentNode->children[childIndex])
return NoMatch {};
currentNode = currentNode->children[childIndex].get();
}

if (currentNode->value.has_value())
return ExactMatch<Value> { currentNode->value.value() };

return PartialMatch {};
}

template <typename Key, typename Value>
bool TrieMap<Key, Value>::contains(Key const& key) const noexcept
{
return std::holds_alternative<ExactMatch<Value>>(search(key));
}

} // namespace crispy
53 changes: 53 additions & 0 deletions src/crispy/TrieMap_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* This file is part of the "libterminal" project
* Copyright (c) 2019-2020 Christian Parpart <christian@parpart.family>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <crispy/TrieMap.h>

#include <catch2/catch.hpp>

#include <cassert>
#include <exception>
#include <iostream>

TEST_CASE("TrieMap.simple")
{
auto m = crispy::TrieMap<std::string, int> {};

m.insert("aa", 1);
m.insert("aba", 2);
m.insert("abb", 3);
REQUIRE(m.size() == 3);

auto const aa = m.search("aa");
CHECK(std::get<crispy::ExactMatch<int>>(aa).value == 1);

auto const ab = m.search("ab");
REQUIRE(std::holds_alternative<crispy::PartialMatch>(ab));

auto const aba = m.search("aba");
REQUIRE(std::holds_alternative<crispy::ExactMatch<int>>(aba));
CHECK(std::get<crispy::ExactMatch<int>>(aba).value == 2);

auto const abb = m.search("abb");
REQUIRE(std::holds_alternative<crispy::ExactMatch<int>>(abb));
CHECK(std::get<crispy::ExactMatch<int>>(abb).value == 3);

auto const abz = m.search("abz");
REQUIRE(std::holds_alternative<crispy::NoMatch>(abz));

m.clear();
REQUIRE(m.size() == 0);
REQUIRE(!m.contains("aa"));
REQUIRE(!m.contains("aba"));
REQUIRE(!m.contains("abb"));
}
3 changes: 0 additions & 3 deletions src/vtbackend/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,9 +775,6 @@ bool Terminal::sendMouseMoveEvent(Modifier modifier,
clearSelection();
}

// TODO(pr): move back to normal mode, if in visual. when mouse is released
// as this is sometimes(???) leaked

// Only continue if he mouse position has changed to a new grid value or we're tracking pixel values.
if (newPosition == _currentMousePosition && !isModeEnabled(DECMode::MouseSGRPixels))
return false;
Expand Down
48 changes: 36 additions & 12 deletions src/vtbackend/ViCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -664,20 +664,21 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count
case ViMotion::CharLeft: // h
{
auto resultPosition = cursorPosition;
// Jumping left to the next non-empty column (whitespace is not considered empty).
// This isn't the most efficient implementation, but it's invoked interactively only anyways.
for (unsigned i = 0; i < count && resultPosition.column > ColumnOffset(0); ++i)
resultPosition = snapToCell(resultPosition - ColumnOffset(1));
while (count)
{
resultPosition = prev(resultPosition);
--count;
}
return resultPosition;
}
case ViMotion::CharRight: // l
{
auto const cellWidth =
std::max(uint8_t { 1 }, _terminal.currentScreen().cellWidthAt(cursorPosition));
auto resultPosition = cursorPosition;
resultPosition.column += ColumnOffset::cast_from(cellWidth);
resultPosition.column =
min(resultPosition.column, ColumnOffset::cast_from(_terminal.pageSize().columns - 1));
while (count)
{
resultPosition = next(resultPosition);
--count;
}
return resultPosition;
}
case ViMotion::ScreenColumn: // |
Expand Down Expand Up @@ -768,9 +769,32 @@ CellLocation ViCommands::translateToCellLocation(ViMotion motion, unsigned count
case ViMotion::ParenthesisMatching: // % TODO
return findMatchingPairFrom(cursorPosition);
case ViMotion::SearchResultBackward: // N TODO
case ViMotion::SearchResultForward: // n TODO
errorlog()("TODO: Missing implementation. Sorry. That will come. :-)");
return cursorPosition;
{
auto startPosition = cursorPosition;
for (unsigned i = 0; i < count; ++i)
{
startPosition = prev(startPosition);
auto const nextPosition = _terminal.searchReverse(startPosition);
if (!nextPosition)
return cursorPosition;

startPosition = *nextPosition;
}
return startPosition;
}
case ViMotion::SearchResultForward: // n
{
auto startPosition = cursorPosition;
for (unsigned i = 0; i < count; ++i)
{
startPosition = next(startPosition);
auto const nextPosition = _terminal.search(startPosition);
if (!nextPosition)
return cursorPosition;
startPosition = *nextPosition;
}
return startPosition;
}
case ViMotion::WordBackward: // b
{
auto current = cursorPosition;
Expand Down
Loading

0 comments on commit 64575c2

Please sign in to comment.