From b173216df1c96bc0e1ff2c73d2cf2dd6d5095ad2 Mon Sep 17 00:00:00 2001
From: Massimo Mund <mo@lancode.de>
Date: Thu, 12 Sep 2024 21:05:58 +0200
Subject: [PATCH] Added `actBackwardSubWord`, `actForwardSubWord`,
 `actKillSubWord` and `actBackwardKillSubWord`

---
 src/actiontype_string.go | 204 ++++++++++++++++++++-------------------
 src/options.go           |   8 ++
 src/terminal.go          |  26 +++++
 3 files changed, 138 insertions(+), 100 deletions(-)

diff --git a/src/actiontype_string.go b/src/actiontype_string.go
index f5221f8abe7..e3dfad4004c 100644
--- a/src/actiontype_string.go
+++ b/src/actiontype_string.go
@@ -23,109 +23,113 @@ func _() {
 	_ = x[actBackwardDeleteChar-12]
 	_ = x[actBackwardDeleteCharEof-13]
 	_ = x[actBackwardWord-14]
-	_ = x[actCancel-15]
-	_ = x[actChangeBorderLabel-16]
-	_ = x[actChangeHeader-17]
-	_ = x[actChangeMulti-18]
-	_ = x[actChangePreviewLabel-19]
-	_ = x[actChangePrompt-20]
-	_ = x[actChangeQuery-21]
-	_ = x[actClearScreen-22]
-	_ = x[actClearQuery-23]
-	_ = x[actClearSelection-24]
-	_ = x[actClose-25]
-	_ = x[actDeleteChar-26]
-	_ = x[actDeleteCharEof-27]
-	_ = x[actEndOfLine-28]
-	_ = x[actFatal-29]
-	_ = x[actForwardChar-30]
-	_ = x[actForwardWord-31]
-	_ = x[actKillLine-32]
-	_ = x[actKillWord-33]
-	_ = x[actUnixLineDiscard-34]
-	_ = x[actUnixWordRubout-35]
-	_ = x[actYank-36]
-	_ = x[actBackwardKillWord-37]
-	_ = x[actSelectAll-38]
-	_ = x[actDeselectAll-39]
-	_ = x[actToggle-40]
-	_ = x[actToggleSearch-41]
-	_ = x[actToggleAll-42]
-	_ = x[actToggleDown-43]
-	_ = x[actToggleUp-44]
-	_ = x[actToggleIn-45]
-	_ = x[actToggleOut-46]
-	_ = x[actToggleTrack-47]
-	_ = x[actToggleTrackCurrent-48]
-	_ = x[actToggleHeader-49]
-	_ = x[actToggleWrap-50]
-	_ = x[actTrackCurrent-51]
-	_ = x[actUntrackCurrent-52]
-	_ = x[actDown-53]
-	_ = x[actUp-54]
-	_ = x[actPageUp-55]
-	_ = x[actPageDown-56]
-	_ = x[actPosition-57]
-	_ = x[actHalfPageUp-58]
-	_ = x[actHalfPageDown-59]
-	_ = x[actOffsetUp-60]
-	_ = x[actOffsetDown-61]
-	_ = x[actOffsetMiddle-62]
-	_ = x[actJump-63]
-	_ = x[actJumpAccept-64]
-	_ = x[actPrintQuery-65]
-	_ = x[actRefreshPreview-66]
-	_ = x[actReplaceQuery-67]
-	_ = x[actToggleSort-68]
-	_ = x[actShowPreview-69]
-	_ = x[actHidePreview-70]
-	_ = x[actTogglePreview-71]
-	_ = x[actTogglePreviewWrap-72]
-	_ = x[actTransform-73]
-	_ = x[actTransformBorderLabel-74]
-	_ = x[actTransformHeader-75]
-	_ = x[actTransformPreviewLabel-76]
-	_ = x[actTransformPrompt-77]
-	_ = x[actTransformQuery-78]
-	_ = x[actPreview-79]
-	_ = x[actChangePreview-80]
-	_ = x[actChangePreviewWindow-81]
-	_ = x[actPreviewTop-82]
-	_ = x[actPreviewBottom-83]
-	_ = x[actPreviewUp-84]
-	_ = x[actPreviewDown-85]
-	_ = x[actPreviewPageUp-86]
-	_ = x[actPreviewPageDown-87]
-	_ = x[actPreviewHalfPageUp-88]
-	_ = x[actPreviewHalfPageDown-89]
-	_ = x[actPrevHistory-90]
-	_ = x[actPrevSelected-91]
-	_ = x[actPrint-92]
-	_ = x[actPut-93]
-	_ = x[actNextHistory-94]
-	_ = x[actNextSelected-95]
-	_ = x[actExecute-96]
-	_ = x[actExecuteSilent-97]
-	_ = x[actExecuteMulti-98]
-	_ = x[actSigStop-99]
-	_ = x[actFirst-100]
-	_ = x[actLast-101]
-	_ = x[actReload-102]
-	_ = x[actReloadSync-103]
-	_ = x[actDisableSearch-104]
-	_ = x[actEnableSearch-105]
-	_ = x[actSelect-106]
-	_ = x[actDeselect-107]
-	_ = x[actUnbind-108]
-	_ = x[actRebind-109]
-	_ = x[actBecome-110]
-	_ = x[actShowHeader-111]
-	_ = x[actHideHeader-112]
+	_ = x[actBackwardSubWord-15]
+	_ = x[actCancel-16]
+	_ = x[actChangeBorderLabel-17]
+	_ = x[actChangeHeader-18]
+	_ = x[actChangeMulti-19]
+	_ = x[actChangePreviewLabel-20]
+	_ = x[actChangePrompt-21]
+	_ = x[actChangeQuery-22]
+	_ = x[actClearScreen-23]
+	_ = x[actClearQuery-24]
+	_ = x[actClearSelection-25]
+	_ = x[actClose-26]
+	_ = x[actDeleteChar-27]
+	_ = x[actDeleteCharEof-28]
+	_ = x[actEndOfLine-29]
+	_ = x[actFatal-30]
+	_ = x[actForwardChar-31]
+	_ = x[actForwardWord-32]
+	_ = x[actForwardSubWord-33]
+	_ = x[actKillLine-34]
+	_ = x[actKillWord-35]
+	_ = x[actKillSubWord-36]
+	_ = x[actUnixLineDiscard-37]
+	_ = x[actUnixWordRubout-38]
+	_ = x[actYank-39]
+	_ = x[actBackwardKillWord-40]
+	_ = x[actBackwardKillSubWord-41]
+	_ = x[actSelectAll-42]
+	_ = x[actDeselectAll-43]
+	_ = x[actToggle-44]
+	_ = x[actToggleSearch-45]
+	_ = x[actToggleAll-46]
+	_ = x[actToggleDown-47]
+	_ = x[actToggleUp-48]
+	_ = x[actToggleIn-49]
+	_ = x[actToggleOut-50]
+	_ = x[actToggleTrack-51]
+	_ = x[actToggleTrackCurrent-52]
+	_ = x[actToggleHeader-53]
+	_ = x[actToggleWrap-54]
+	_ = x[actTrackCurrent-55]
+	_ = x[actUntrackCurrent-56]
+	_ = x[actDown-57]
+	_ = x[actUp-58]
+	_ = x[actPageUp-59]
+	_ = x[actPageDown-60]
+	_ = x[actPosition-61]
+	_ = x[actHalfPageUp-62]
+	_ = x[actHalfPageDown-63]
+	_ = x[actOffsetUp-64]
+	_ = x[actOffsetDown-65]
+	_ = x[actOffsetMiddle-66]
+	_ = x[actJump-67]
+	_ = x[actJumpAccept-68]
+	_ = x[actPrintQuery-69]
+	_ = x[actRefreshPreview-70]
+	_ = x[actReplaceQuery-71]
+	_ = x[actToggleSort-72]
+	_ = x[actShowPreview-73]
+	_ = x[actHidePreview-74]
+	_ = x[actTogglePreview-75]
+	_ = x[actTogglePreviewWrap-76]
+	_ = x[actTransform-77]
+	_ = x[actTransformBorderLabel-78]
+	_ = x[actTransformHeader-79]
+	_ = x[actTransformPreviewLabel-80]
+	_ = x[actTransformPrompt-81]
+	_ = x[actTransformQuery-82]
+	_ = x[actPreview-83]
+	_ = x[actChangePreview-84]
+	_ = x[actChangePreviewWindow-85]
+	_ = x[actPreviewTop-86]
+	_ = x[actPreviewBottom-87]
+	_ = x[actPreviewUp-88]
+	_ = x[actPreviewDown-89]
+	_ = x[actPreviewPageUp-90]
+	_ = x[actPreviewPageDown-91]
+	_ = x[actPreviewHalfPageUp-92]
+	_ = x[actPreviewHalfPageDown-93]
+	_ = x[actPrevHistory-94]
+	_ = x[actPrevSelected-95]
+	_ = x[actPrint-96]
+	_ = x[actPut-97]
+	_ = x[actNextHistory-98]
+	_ = x[actNextSelected-99]
+	_ = x[actExecute-100]
+	_ = x[actExecuteSilent-101]
+	_ = x[actExecuteMulti-102]
+	_ = x[actSigStop-103]
+	_ = x[actFirst-104]
+	_ = x[actLast-105]
+	_ = x[actReload-106]
+	_ = x[actReloadSync-107]
+	_ = x[actDisableSearch-108]
+	_ = x[actEnableSearch-109]
+	_ = x[actSelect-110]
+	_ = x[actDeselect-111]
+	_ = x[actUnbind-112]
+	_ = x[actRebind-113]
+	_ = x[actBecome-114]
+	_ = x[actShowHeader-115]
+	_ = x[actHideHeader-116]
 }
 
-const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
+const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
 
-var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
+var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 216, 225, 245, 260, 274, 295, 310, 324, 338, 351, 368, 376, 389, 405, 417, 425, 439, 453, 470, 481, 492, 506, 524, 541, 548, 567, 589, 601, 615, 624, 639, 651, 664, 675, 686, 698, 712, 733, 748, 761, 776, 793, 800, 805, 814, 825, 836, 849, 864, 875, 888, 903, 910, 923, 936, 953, 968, 981, 995, 1009, 1025, 1045, 1057, 1080, 1098, 1122, 1140, 1157, 1167, 1183, 1205, 1218, 1234, 1246, 1260, 1276, 1294, 1314, 1336, 1350, 1365, 1373, 1379, 1393, 1408, 1418, 1434, 1449, 1459, 1467, 1474, 1483, 1496, 1512, 1527, 1536, 1547, 1556, 1565, 1574, 1587, 1600}
 
 func (i actionType) String() string {
 	if i < 0 || i >= actionType(len(_actionType_index)-1) {
diff --git a/src/options.go b/src/options.go
index 55030bd2d13..1a71afa831f 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1317,6 +1317,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
 			appendAction(actBackwardDeleteCharEof)
 		case "backward-word":
 			appendAction(actBackwardWord)
+		case "backward-subword":
+			appendAction(actBackwardSubWord)
 		case "clear-screen":
 			appendAction(actClearScreen)
 		case "delete-char":
@@ -1337,6 +1339,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
 			appendAction(actForwardChar)
 		case "forward-word":
 			appendAction(actForwardWord)
+		case "forward-subword":
+			appendAction(actForwardSubWord)
 		case "jump":
 			appendAction(actJump)
 		case "jump-accept":
@@ -1345,6 +1349,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
 			appendAction(actKillLine)
 		case "kill-word":
 			appendAction(actKillWord)
+		case "kill-subword":
+			appendAction(actKillSubWord)
 		case "unix-line-discard", "line-discard":
 			appendAction(actUnixLineDiscard)
 		case "unix-word-rubout", "word-rubout":
@@ -1353,6 +1359,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
 			appendAction(actYank)
 		case "backward-kill-word":
 			appendAction(actBackwardKillWord)
+		case "backward-kill-subword":
+			appendAction(actBackwardKillSubWord)
 		case "toggle-down":
 			appendAction(actToggle, actDown)
 		case "toggle-up":
diff --git a/src/terminal.go b/src/terminal.go
index 1158de7ac89..d8c316fb782 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -247,6 +247,8 @@ type Terminal struct {
 	scrollOff          int
 	wordRubout         string
 	wordNext           string
+	subWordRubout      string
+	subWordNext        string
 	cx                 int
 	cy                 int
 	offset             int
@@ -416,6 +418,7 @@ const (
 	actBackwardDeleteChar
 	actBackwardDeleteCharEof
 	actBackwardWord
+	actBackwardSubWord
 	actCancel
 	actChangeBorderLabel
 	actChangeHeader
@@ -433,12 +436,15 @@ const (
 	actFatal
 	actForwardChar
 	actForwardWord
+	actForwardSubWord
 	actKillLine
 	actKillWord
+	actKillSubWord
 	actUnixLineDiscard
 	actUnixWordRubout
 	actYank
 	actBackwardKillWord
+	actBackwardKillSubWord
 	actSelectAll
 	actDeselectAll
 	actToggle
@@ -755,6 +761,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
 	}
 	wordRubout := "[^\\pL\\pN][\\pL\\pN]"
 	wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
+	subWordRubout := "[a-z][A-Z]|[^\\pL\\pN][\\pL\\pN]"
+	subWordNext := "[a-z][A-Z]|[\\pL\\pN][^\\pL\\pN]|(.$)"
 	if opts.FileWord {
 		sep := regexp.QuoteMeta(string(os.PathSeparator))
 		wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
@@ -787,6 +795,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
 		markerMultiLine:    *opts.MarkerMulti,
 		wordRubout:         wordRubout,
 		wordNext:           wordNext,
+		subWordRubout:      subWordRubout,
+		subWordNext:        subWordNext,
 		cx:                 len(input),
 		cy:                 0,
 		offset:             0,
@@ -4383,6 +4393,11 @@ func (t *Terminal) Loop() error {
 				if t.cx > 0 {
 					t.rubout(t.wordRubout)
 				}
+			case actBackwardKillSubWord:
+				beof = len(t.input) == 0
+				if t.cx > 0 {
+					t.rubout(t.subWordRubout)
+				}
 			case actYank:
 				suffix := copySlice(t.input[t.cx:])
 				t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
@@ -4433,6 +4448,10 @@ func (t *Terminal) Loop() error {
 				t.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1
 			case actForwardWord:
 				t.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
+			case actBackwardSubWord:
+				t.cx = findLastMatch(t.subWordRubout, string(t.input[:t.cx])) + 1
+			case actForwardSubWord:
+				t.cx += findFirstMatch(t.subWordNext, string(t.input[t.cx:])) + 1
 			case actKillWord:
 				ncx := t.cx +
 					findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
@@ -4440,6 +4459,13 @@ func (t *Terminal) Loop() error {
 					t.yanked = copySlice(t.input[t.cx:ncx])
 					t.input = append(t.input[:t.cx], t.input[ncx:]...)
 				}
+			case actKillSubWord:
+				ncx := t.cx +
+					findFirstMatch(t.subWordNext, string(t.input[t.cx:])) + 1
+				if ncx > t.cx {
+					t.yanked = copySlice(t.input[t.cx:ncx])
+					t.input = append(t.input[:t.cx], t.input[ncx:]...)
+				}
 			case actKillLine:
 				if t.cx < len(t.input) {
 					t.yanked = copySlice(t.input[t.cx:])