@@ -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+
10111072void Input::syncPasswordGlyphNodes (std::size_t count) {
10121073 if (m_textViewport == nullptr ) {
10131074 return ;
0 commit comments