diff --git a/include/string.hpp b/include/string.hpp index 146b359e..3395a527 100644 --- a/include/string.hpp +++ b/include/string.hpp @@ -47,6 +47,16 @@ bool startsWith(const std::string& str, const std::string& prefix); /** Returns whether a string ends with the given substring. */ bool endsWith(const std::string& str, const std::string& suffix); +struct Location { + /** Line number, 0-indexed */ + size_t line; + /** UTF-8 codepoint index, 0-indexed */ + size_t column; +}; +/** Given a byte position of s, returns the 2D UTF-8 location of the cursor. */ +Location positionToLocation(const std::string& s, size_t pos); +size_t locationToPosition(const std::string& s, Location location); + /** Converts a byte array to a Base64-encoded string. https://en.wikipedia.org/wiki/Base64 */ diff --git a/src/string.cpp b/src/string.cpp index d9ae1ecd..23d8b812 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -155,6 +155,52 @@ bool endsWith(const std::string& str, const std::string& suffix) { } +Location positionToLocation(const std::string& s, size_t pos) { + if (pos > s.size()) + pos = s.size(); + + // Count number of newlines until pos + size_t line = 0; + size_t linePos = 0; + for (size_t i = 0; i < pos; i++) { + if (s[i] == '\n') { + line++; + linePos = i + 1; + } + } + + // Count number of codepoints from line until pos + size_t column = 0; + for (size_t i = linePos; i < pos;) { + i = UTF8NextCodepoint(s, i); + column++; + } + + return {line, column}; +} + + +size_t locationToPosition(const std::string& s, Location location) { + size_t pos = 0; + + // Advance `location.line` newlines + for (size_t lines = 0; lines < location.line && pos < s.size(); pos++) { + if (s[pos] == '\n') + lines++; + } + + // Advance `location.column` codepoints + for (size_t columns = 0; columns < location.column && pos < s.size(); columns++) { + // Stop if column is beyond newline + if (s[pos] == '\n') + break; + pos = UTF8NextCodepoint(s, pos); + } + + return pos; +} + + std::string toBase64(const uint8_t* data, size_t dataLen) { static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index f2830530..ce38a1ff 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -198,10 +198,21 @@ void TextField::onSelectKey(const SelectKeyEvent& e) { } // Up (placeholder) if (e.isKeyCommand(GLFW_KEY_UP)) { + string::Location location = string::positionToLocation(text, cursor); + if (location.line == 0) + location.column = 0; + else + location.line--; + cursor = locationToPosition(text, location); + selection = cursor; e.consume(this); } // Down (placeholder) if (e.isKeyCommand(GLFW_KEY_DOWN)) { + string::Location location = string::positionToLocation(text, cursor); + location.line++; + cursor = locationToPosition(text, location); + selection = cursor; e.consume(this); } // Home