Repository: junegunn/fzf (language: go)

Issue:
Let toggle-track disable track action
Currently the `toggle-track` action does not toggle off the tracking of the current selection started by the `track` action. The only way of disabling it is by changing the focus.

Change this by treating `trackCurrent` like `trackEnabled`.


Discussion / hints:
Maybe off-topic:

What I noticed too: The `track` action currently only switches over to `trackCurrent` if tracking is disabled at the moment (see [terminal.go#L3791](https://github.com/junegunn/fzf/blob/db6db49ed642/src/terminal.go#L3791-L3793)). Wouldn't it make sense to do it always (i.e. also on `trackEnabled`) since the description of `track` states that the tracking will be disabled on focus change?

I do have to say that the differentiation between `Enabled` and `Current` was not very clear to me as a user before reading the source code. Maybe a name like `track-current` would suite better for the `track` action to emphasize the difference between the way it works and the tracking from `--track` and `toggle-track`?

Just my 2cts on this topic that might be slightly related to this PR.
I understand. There can be confusion between the global tracking state set by `--track` / `toggle-track` and a one-off `track` action.

The suggested change will make `toggle-track` affect the latter, but I'm concerned that it might cause more confusion. A user might accidentally turn on the global tracking state on the second invocation of `toggle-track`. So here's what I'm suggesting in addition to your proposal.

* Rename `track` to `track-current`, but keep the old name for backward compatibility
* Add `untrack-current` action that only affects the local tracking state
* Leave `toggle-track` unchanged. It should only affect the global tracking state

Or we could just add `untrack` action and clarify the distinction on the man page.
I see your point, yes those changes including the rename would probably make it clearer.

What about the behavior of FZF when `track-current` is executed while the global tracking is enabled? Should it stay like now that it does nothing?


I think `track-current` (a.k.a. `track`) and `untrack-current` should both be no-op when global tracking is enabled.

Anyway, what was your primary motivation behind this? I guess you're not using `--track` but `track` action and wanted a way to "untrack" it?

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c239efabb41..cc1e8fa146c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,12 @@
 CHANGELOG
 =========
 
-0.48.2
+0.49.0
 ------
+- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action
+    - `track` is still available as an alias
+- Added `untrack-current` and `toggle-track-current` actions
+    - `*-current` actions are no-op when the global tracking state is set
 - Bug fixes
 
 0.48.1
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index f86aa15949c..a02b45c17f8 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -1326,9 +1326,10 @@ A key or an event can be bound to one or more of the following actions.
     \fBtoggle-preview-wrap\fR
     \fBtoggle-search\fR                (toggle search functionality)
     \fBtoggle-sort\fR
-    \fBtoggle-track\fR
+    \fBtoggle-track\fR                 (toggle global tracking option (\fB--track\fR))
+    \fBtoggle-track-current\fR         (toggle tracking of the current item)
     \fBtoggle+up\fR                    \fIbtab    (shift-tab)\fR
-    \fBtrack\fR                        (track the current item; automatically disabled if focus changes)
+    \fBtrack-current\fR                (track the current item; automatically disabled if focus changes)
     \fBtransform(...)\fR               (transform states using the output of an external command)
     \fBtransform-border-label(...)\fR  (transform border label using an external command)
     \fBtransform-header(...)\fR        (transform header using an external command)
@@ -1338,6 +1339,7 @@ A key or an event can be bound to one or more of the following actions.
     \fBunbind(...)\fR                  (unbind bindings)
     \fBunix-line-discard\fR            \fIctrl-u\fR
     \fBunix-word-rubout\fR             \fIctrl-w\fR
+    \fBuntrack-current\fR              (stop tracking the current item; no-op if global tracking is enabled)
     \fBup\fR                           \fIctrl-k  ctrl-p  up\fR
     \fByank\fR                         \fIctrl-y\fR
 
diff --git a/src/actiontype_string.go b/src/actiontype_string.go
index 4ab27611223..341c4bd4bcf 100644
--- a/src/actiontype_string.go
+++ b/src/actiontype_string.go
@@ -54,72 +54,74 @@ func _() {
 	_ = x[actToggleIn-43]
 	_ = x[actToggleOut-44]
 	_ = x[actToggleTrack-45]
-	_ = x[actToggleHeader-46]
-	_ = x[actTrack-47]
-	_ = x[actDown-48]
-	_ = x[actUp-49]
-	_ = x[actPageUp-50]
-	_ = x[actPageDown-51]
-	_ = x[actPosition-52]
-	_ = x[actHalfPageUp-53]
-	_ = x[actHalfPageDown-54]
-	_ = x[actOffsetUp-55]
-	_ = x[actOffsetDown-56]
-	_ = x[actJump-57]
-	_ = x[actJumpAccept-58]
-	_ = x[actPrintQuery-59]
-	_ = x[actRefreshPreview-60]
-	_ = x[actReplaceQuery-61]
-	_ = x[actToggleSort-62]
-	_ = x[actShowPreview-63]
-	_ = x[actHidePreview-64]
-	_ = x[actTogglePreview-65]
-	_ = x[actTogglePreviewWrap-66]
-	_ = x[actTransform-67]
-	_ = x[actTransformBorderLabel-68]
-	_ = x[actTransformHeader-69]
-	_ = x[actTransformPreviewLabel-70]
-	_ = x[actTransformPrompt-71]
-	_ = x[actTransformQuery-72]
-	_ = x[actPreview-73]
-	_ = x[actChangePreview-74]
-	_ = x[actChangePreviewWindow-75]
-	_ = x[actPreviewTop-76]
-	_ = x[actPreviewBottom-77]
-	_ = x[actPreviewUp-78]
-	_ = x[actPreviewDown-79]
-	_ = x[actPreviewPageUp-80]
-	_ = x[actPreviewPageDown-81]
-	_ = x[actPreviewHalfPageUp-82]
-	_ = x[actPreviewHalfPageDown-83]
-	_ = x[actPrevHistory-84]
-	_ = x[actPrevSelected-85]
-	_ = x[actPut-86]
-	_ = x[actNextHistory-87]
-	_ = x[actNextSelected-88]
-	_ = x[actExecute-89]
-	_ = x[actExecuteSilent-90]
-	_ = x[actExecuteMulti-91]
-	_ = x[actSigStop-92]
-	_ = x[actFirst-93]
-	_ = x[actLast-94]
-	_ = x[actReload-95]
-	_ = x[actReloadSync-96]
-	_ = x[actDisableSearch-97]
-	_ = x[actEnableSearch-98]
-	_ = x[actSelect-99]
-	_ = x[actDeselect-100]
-	_ = x[actUnbind-101]
-	_ = x[actRebind-102]
-	_ = x[actBecome-103]
-	_ = x[actResponse-104]
-	_ = x[actShowHeader-105]
-	_ = x[actHideHeader-106]
+	_ = x[actToggleTrackCurrent-46]
+	_ = x[actToggleHeader-47]
+	_ = x[actTrackCurrent-48]
+	_ = x[actUntrackCurrent-49]
+	_ = x[actDown-50]
+	_ = x[actUp-51]
+	_ = x[actPageUp-52]
+	_ = x[actPageDown-53]
+	_ = x[actPosition-54]
+	_ = x[actHalfPageUp-55]
+	_ = x[actHalfPageDown-56]
+	_ = x[actOffsetUp-57]
+	_ = x[actOffsetDown-58]
+	_ = x[actJump-59]
+	_ = x[actJumpAccept-60]
+	_ = x[actPrintQuery-61]
+	_ = x[actRefreshPreview-62]
+	_ = x[actReplaceQuery-63]
+	_ = x[actToggleSort-64]
+	_ = x[actShowPreview-65]
+	_ = x[actHidePreview-66]
+	_ = x[actTogglePreview-67]
+	_ = x[actTogglePreviewWrap-68]
+	_ = x[actTransform-69]
+	_ = x[actTransformBorderLabel-70]
+	_ = x[actTransformHeader-71]
+	_ = x[actTransformPreviewLabel-72]
+	_ = x[actTransformPrompt-73]
+	_ = x[actTransformQuery-74]
+	_ = x[actPreview-75]
+	_ = x[actChangePreview-76]
+	_ = x[actChangePreviewWindow-77]
+	_ = x[actPreviewTop-78]
+	_ = x[actPreviewBottom-79]
+	_ = x[actPreviewUp-80]
+	_ = x[actPreviewDown-81]
+	_ = x[actPreviewPageUp-82]
+	_ = x[actPreviewPageDown-83]
+	_ = x[actPreviewHalfPageUp-84]
+	_ = x[actPreviewHalfPageDown-85]
+	_ = x[actPrevHistory-86]
+	_ = x[actPrevSelected-87]
+	_ = x[actPut-88]
+	_ = x[actNextHistory-89]
+	_ = x[actNextSelected-90]
+	_ = x[actExecute-91]
+	_ = x[actExecuteSilent-92]
+	_ = x[actExecuteMulti-93]
+	_ = x[actSigStop-94]
+	_ = x[actFirst-95]
+	_ = x[actLast-96]
+	_ = x[actReload-97]
+	_ = x[actReloadSync-98]
+	_ = x[actDisableSearch-99]
+	_ = x[actEnableSearch-100]
+	_ = x[actSelect-101]
+	_ = x[actDeselect-102]
+	_ = x[actUnbind-103]
+	_ = x[actRebind-104]
+	_ = x[actBecome-105]
+	_ = x[actResponse-106]
+	_ = x[actShowHeader-107]
+	_ = x[actHideHeader-108]
 }
 
-const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
+const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
 
-var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
+var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 640, 655, 670, 687, 694, 699, 708, 719, 730, 743, 758, 769, 782, 789, 802, 815, 832, 847, 860, 874, 888, 904, 924, 936, 959, 977, 1001, 1019, 1036, 1046, 1062, 1084, 1097, 1113, 1125, 1139, 1155, 1173, 1193, 1215, 1229, 1244, 1250, 1264, 1279, 1289, 1305, 1320, 1330, 1338, 1345, 1354, 1367, 1383, 1398, 1407, 1418, 1427, 1436, 1445, 1456, 1469, 1482}
 
 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 692ce6b29d2..a28e9e6f56c 100644
--- a/src/options.go
+++ b/src/options.go
@@ -1211,14 +1211,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
 			appendAction(actToggleSearch)
 		case "toggle-track":
 			appendAction(actToggleTrack)
+		case "toggle-track-current":
+			appendAction(actToggleTrackCurrent)
 		case "toggle-header":
 			appendAction(actToggleHeader)
 		case "show-header":
 			appendAction(actShowHeader)
 		case "hide-header":
 			appendAction(actHideHeader)
-		case "track":
-			appendAction(actTrack)
+		case "track", "track-current":
+			appendAction(actTrackCurrent)
+		case "untrack-current":
+			appendAction(actUntrackCurrent)
 		case "select":
 			appendAction(actSelect)
 		case "select-all":
diff --git a/src/terminal.go b/src/terminal.go
index c2eda995fa8..726cb498f74 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -394,8 +394,10 @@ const (
 	actToggleIn
 	actToggleOut
 	actToggleTrack
+	actToggleTrackCurrent
 	actToggleHeader
-	actTrack
+	actTrackCurrent
+	actUntrackCurrent
 	actDown
 	actUp
 	actPageUp
@@ -1645,8 +1647,11 @@ func (t *Terminal) printInfo() {
 			output += " -S"
 		}
 	}
-	if t.track != trackDisabled {
+	switch t.track {
+	case trackEnabled:
 		output += " +T"
+	case trackCurrent:
+		output += " +t"
 	}
 	if t.multi > 0 {
 		if t.multi == maxMulti {
@@ -3778,6 +3783,14 @@ func (t *Terminal) Loop() {
 					t.track = trackEnabled
 				}
 				req(reqInfo)
+			case actToggleTrackCurrent:
+				switch t.track {
+				case trackCurrent:
+					t.track = trackDisabled
+				case trackDisabled:
+					t.track = trackCurrent
+				}
+				req(reqInfo)
 			case actShowHeader:
 				t.headerVisible = true
 				req(reqList, reqInfo, reqPrompt, reqHeader)
@@ -3787,11 +3800,16 @@ func (t *Terminal) Loop() {
 			case actToggleHeader:
 				t.headerVisible = !t.headerVisible
 				req(reqList, reqInfo, reqPrompt, reqHeader)
-			case actTrack:
+			case actTrackCurrent:
 				if t.track == trackDisabled {
 					t.track = trackCurrent
 				}
 				req(reqInfo)
+			case actUntrackCurrent:
+				if t.track == trackCurrent {
+					t.track = trackDisabled
+				}
+				req(reqInfo)
 			case actEnableSearch:
 				t.paused = false
 				changed = true


Test patch:
diff --git a/test/test_go.rb b/test/test_go.rb
index 10d4ef405e0..f44f6379a9b 100755
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -3080,7 +3080,7 @@ def test_track_action
     end
     tmux.send_keys :t
     tmux.until do |lines|
-      assert_includes lines[-2], '+T'
+      assert_includes lines[-2], '+t'
     end
     tmux.send_keys :BSpace
     tmux.until do |lines|
@@ -3092,7 +3092,7 @@ def test_track_action
     tmux.send_keys '4'
     tmux.until do |lines|
       assert_equal 28, lines.match_count
-      refute_includes lines[-2], '+T'
+      refute_includes lines[-2], '+t'
     end
     tmux.send_keys :BSpace
     tmux.until do |lines|
@@ -3101,11 +3101,11 @@ def test_track_action
     end
     tmux.send_keys :t
     tmux.until do |lines|
-      assert_includes lines[-2], '+T'
+      assert_includes lines[-2], '+t'
     end
     tmux.send_keys :Up
     tmux.until do |lines|
-      refute_includes lines[-2], '+T'
+      refute_includes lines[-2], '+t'
     end
   end