Skip to content

Commit 3f3b8eb

Browse files
committed
feat(input): added support for more Ctrl+Shift/Backspace combos
1 parent e3d9fee commit 3f3b8eb

2 files changed

Lines changed: 72 additions & 8 deletions

File tree

src/ui/controls/input.cpp

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,8 @@ void Input::handleKey(std::uint32_t sym, std::uint32_t utf32, std::uint32_t modi
585585
// Ignore keys that produce no text and aren't action keys we handle below
586586
if (utf32 == 0 && !preedit) {
587587
const bool navigationOrEdit = sym == XKB_KEY_BackSpace || sym == XKB_KEY_Delete || sym == XKB_KEY_Left ||
588-
sym == XKB_KEY_Right || sym == XKB_KEY_Home || sym == XKB_KEY_End;
588+
sym == XKB_KEY_Right || sym == XKB_KEY_Home || sym == XKB_KEY_End ||
589+
sym == XKB_KEY_Insert;
589590
if (!navigationOrEdit && !validateMatch) {
590591
return;
591592
}
@@ -604,21 +605,26 @@ void Input::handleKey(std::uint32_t sym, std::uint32_t utf32, std::uint32_t modi
604605
changed = true;
605606
}
606607

608+
const bool copyShortcut = ctrl && (sym == XKB_KEY_Insert || sym == 'c' || sym == 'C');
609+
const bool cutShortcut =
610+
(ctrl && (sym == 'x' || sym == 'X')) || (!ctrl && shift && sym == XKB_KEY_Delete && hasSelection());
611+
const bool pasteShortcut = (ctrl && (sym == 'v' || sym == 'V')) || (!ctrl && shift && sym == XKB_KEY_Insert);
612+
607613
if (ctrl && (sym == 'a' || sym == 'A')) {
608614
// Select all
609615
m_selectionAnchor = 0;
610616
m_cursorPos = m_value.size();
611-
} else if (ctrl && (sym == 'c' || sym == 'C')) {
617+
} else if (copyShortcut) {
612618
if (g_clipboard != nullptr && hasSelection()) {
613619
g_clipboard->copyText(m_value.substr(selectionStart(), selectionEnd() - selectionStart()));
614620
}
615-
} else if (ctrl && (sym == 'x' || sym == 'X')) {
621+
} else if (cutShortcut) {
616622
if (g_clipboard != nullptr && hasSelection()) {
617623
g_clipboard->copyText(m_value.substr(selectionStart(), selectionEnd() - selectionStart()));
618624
deleteSelection();
619625
changed = true;
620626
}
621-
} else if (ctrl && (sym == 'v' || sym == 'V')) {
627+
} else if (pasteShortcut) {
622628
if (g_clipboard != nullptr) {
623629
if (auto text = readClipboardText(); text.has_value()) {
624630
if (hasSelection()) {
@@ -635,7 +641,7 @@ void Input::handleKey(std::uint32_t sym, std::uint32_t utf32, std::uint32_t modi
635641
deleteSelection();
636642
changed = true;
637643
} else if (m_cursorPos > 0) {
638-
const std::size_t prev = prevCharPos(m_value, m_cursorPos);
644+
const std::size_t prev = ctrl ? previousWordStartForByteOffset(m_cursorPos) : prevCharPos(m_value, m_cursorPos);
639645
m_value.erase(prev, m_cursorPos - prev);
640646
m_cursorPos = prev;
641647
m_selectionAnchor = prev;
@@ -646,7 +652,7 @@ void Input::handleKey(std::uint32_t sym, std::uint32_t utf32, std::uint32_t modi
646652
deleteSelection();
647653
changed = true;
648654
} else if (m_cursorPos < m_value.size()) {
649-
const std::size_t next = nextCharPos(m_value, m_cursorPos);
655+
const std::size_t next = ctrl ? nextWordEndForByteOffset(m_cursorPos) : nextCharPos(m_value, m_cursorPos);
650656
m_value.erase(m_cursorPos, next - m_cursorPos);
651657
changed = true;
652658
}
@@ -656,7 +662,7 @@ void Input::handleKey(std::uint32_t sym, std::uint32_t utf32, std::uint32_t modi
656662
m_cursorPos = selectionStart();
657663
m_selectionAnchor = m_cursorPos;
658664
} else {
659-
m_cursorPos = prevCharPos(m_value, m_cursorPos);
665+
m_cursorPos = ctrl ? previousWordStartForByteOffset(m_cursorPos) : prevCharPos(m_value, m_cursorPos);
660666
if (!shift) {
661667
m_selectionAnchor = m_cursorPos;
662668
}
@@ -667,7 +673,7 @@ void Input::handleKey(std::uint32_t sym, std::uint32_t utf32, std::uint32_t modi
667673
m_cursorPos = selectionEnd();
668674
m_selectionAnchor = m_cursorPos;
669675
} else {
670-
m_cursorPos = nextCharPos(m_value, m_cursorPos);
676+
m_cursorPos = ctrl ? nextWordStartForByteOffset(m_cursorPos) : nextCharPos(m_value, m_cursorPos);
671677
if (!shift) {
672678
m_selectionAnchor = m_cursorPos;
673679
}
@@ -1008,6 +1014,61 @@ std::size_t Input::wordEndForByteOffset(std::size_t offset) const {
10081014
return end;
10091015
}
10101016

1017+
std::size_t Input::previousWordStartForByteOffset(std::size_t offset) const {
1018+
if (m_value.empty()) {
1019+
return 0;
1020+
}
1021+
1022+
std::size_t pos = std::min(offset, m_value.size());
1023+
while (pos > 0) {
1024+
const std::size_t prev = prevCharPos(m_value, pos);
1025+
if (prev == pos || isWordCodepoint(m_value, prev)) {
1026+
break;
1027+
}
1028+
pos = prev;
1029+
}
1030+
while (pos > 0) {
1031+
const std::size_t prev = prevCharPos(m_value, pos);
1032+
if (prev == pos || !isWordCodepoint(m_value, prev)) {
1033+
break;
1034+
}
1035+
pos = prev;
1036+
}
1037+
return pos;
1038+
}
1039+
1040+
std::size_t Input::nextWordStartForByteOffset(std::size_t offset) const {
1041+
if (m_value.empty()) {
1042+
return 0;
1043+
}
1044+
1045+
std::size_t pos = std::min(offset, m_value.size());
1046+
if (pos < m_value.size() && isWordCodepoint(m_value, pos)) {
1047+
while (pos < m_value.size() && isWordCodepoint(m_value, pos)) {
1048+
pos = nextCharPos(m_value, pos);
1049+
}
1050+
}
1051+
while (pos < m_value.size() && !isWordCodepoint(m_value, pos)) {
1052+
pos = nextCharPos(m_value, pos);
1053+
}
1054+
return pos;
1055+
}
1056+
1057+
std::size_t Input::nextWordEndForByteOffset(std::size_t offset) const {
1058+
if (m_value.empty()) {
1059+
return 0;
1060+
}
1061+
1062+
std::size_t pos = std::min(offset, m_value.size());
1063+
while (pos < m_value.size() && !isWordCodepoint(m_value, pos)) {
1064+
pos = nextCharPos(m_value, pos);
1065+
}
1066+
while (pos < m_value.size() && isWordCodepoint(m_value, pos)) {
1067+
pos = nextCharPos(m_value, pos);
1068+
}
1069+
return pos;
1070+
}
1071+
10111072
void Input::syncPasswordGlyphNodes(std::size_t count) {
10121073
if (m_textViewport == nullptr) {
10131074
return;

src/ui/controls/input.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ class Input : public Node {
8282
void selectWordAtByteOffset(std::size_t offset);
8383
[[nodiscard]] std::size_t wordStartForByteOffset(std::size_t offset) const;
8484
[[nodiscard]] std::size_t wordEndForByteOffset(std::size_t offset) const;
85+
[[nodiscard]] std::size_t previousWordStartForByteOffset(std::size_t offset) const;
86+
[[nodiscard]] std::size_t nextWordStartForByteOffset(std::size_t offset) const;
87+
[[nodiscard]] std::size_t nextWordEndForByteOffset(std::size_t offset) const;
8588
[[nodiscard]] float textViewportWidth() const noexcept;
8689
[[nodiscard]] bool clearButtonVisible() const noexcept;
8790
[[nodiscard]] float clearButtonHitWidth() const noexcept;

0 commit comments

Comments
 (0)