Repository: rsteube/carapace
Pull request #804 (2023-07-12 09:08:15)

action: added Retain
exposed filter



Patch:
diff --git a/action.go b/action.go
index d9b4630..77f3c51 100644
--- a/action.go
+++ b/action.go
@@ -52,6 +52,32 @@ func (a Action) Cache(timeout time.Duration, keys ...pkgcache.Key) Action {
 	return a
 }
 
+// Chdir changes the current working directory to the named directory for the duration of invocation.
+func (a Action) Chdir(dir string) Action {
+	return ActionCallback(func(c Context) Action {
+		abs, err := c.Abs(dir)
+		if err != nil {
+			return ActionMessage(err.Error())
+		}
+		if info, err := os.Stat(abs); err != nil {
+			return ActionMessage(err.Error())
+		} else if !info.IsDir() {
+			return ActionMessage("not a directory: %v", abs)
+		}
+		c.Dir = abs
+		return a.Invoke(c).ToA()
+	})
+}
+
+// Filter filters given values.
+//
+//	carapace.ActionValues("A", "B", "C").Filter([]string{"B"}) // ["A", "C"]
+func (a Action) Filter(values []string) Action {
+	return ActionCallback(func(c Context) Action {
+		return a.Invoke(c).Filter(values).ToA()
+	})
+}
+
 // Invoke executes the callback of an action if it exists (supports nesting).
 func (a Action) Invoke(c Context) InvokedAction {
 	if c.Args == nil {
@@ -72,6 +98,20 @@ func (a Action) Invoke(c Context) InvokedAction {
 	return InvokedAction{a}
 }
 
+// List wraps the Action in an ActionMultiParts with given divider.
+func (a Action) List(divider string) Action {
+	return ActionMultiParts(divider, func(c Context) Action {
+		return a.Invoke(c).ToA().NoSpace()
+	})
+}
+
+// MultiParts splits values of an Action by given dividers and completes each segment separately.
+func (a Action) MultiParts(dividers ...string) Action {
+	return ActionCallback(func(c Context) Action {
+		return a.Invoke(c).ToMultiPartsA(dividers...)
+	})
+}
+
 // NoSpace disables space suffix for given characters (or all if none are given).
 func (a Action) NoSpace(suffixes ...rune) Action {
 	return ActionCallback(func(c Context) Action {
@@ -83,20 +123,21 @@ func (a Action) NoSpace(suffixes ...rune) Action {
 	})
 }
 
-// Usage sets the usage.
-func (a Action) Usage(usage string, args ...interface{}) Action {
-	return a.UsageF(func() string {
-		return fmt.Sprintf(usage, args...)
+// Prefix adds a prefix to values (only the ones inserted, not the display values).
+//
+//	carapace.ActionValues("melon", "drop", "fall").Prefix("water")
+func (a Action) Prefix(prefix string) Action {
+	return ActionCallback(func(c Context) Action {
+		return a.Invoke(c).Prefix(prefix).ToA()
 	})
 }
 
-// Usage sets the usage using a function.
-func (a Action) UsageF(f func() string) Action {
+// Retain retains given values.
+//
+//	carapace.ActionValues("A", "B", "C").Retain([]string{"A", "C"}) // ["A", "C"]
+func (a Action) Retain(values []string) Action {
 	return ActionCallback(func(c Context) Action {
-		if usage := f(); usage != "" {
-			a.meta.Usage = usage
-		}
-		return a
+		return a.Invoke(c).Retain(values).ToA()
 	})
 }
 
@@ -110,6 +151,20 @@ func (a Action) Style(s string) Action {
 	})
 }
 
+// Style sets the style using a function.
+//
+//	ActionValues("dir/", "test.txt").StyleF(style.ForPathExt)
+//	ActionValues("true", "false").StyleF(style.ForKeyword)
+func (a Action) StyleF(f func(s string, sc style.Context) string) Action {
+	return ActionCallback(func(c Context) Action {
+		invoked := a.Invoke(c)
+		for index, v := range invoked.rawValues {
+			invoked.rawValues[index].Style = f(v.Value, c)
+		}
+		return invoked.ToA()
+	})
+}
+
 // Style sets the style using a reference.
 //
 //	ActionValues("value").StyleR(&style.Carapace.Value)
@@ -123,15 +178,21 @@ func (a Action) StyleR(s *string) Action {
 	})
 }
 
-// Style sets the style using a function.
+// Suffix adds a suffx to values (only the ones inserted, not the display values).
 //
-//	ActionValues("dir/", "test.txt").StyleF(style.ForPathExt)
-//	ActionValues("true", "false").StyleF(style.ForKeyword)
-func (a Action) StyleF(f func(s string, sc style.Context) string) Action {
+//	carapace.ActionValues("apple", "melon", "orange").Suffix("juice")
+func (a Action) Suffix(suffix string) Action {
+	return ActionCallback(func(c Context) Action {
+		return a.Invoke(c).Suffix(suffix).ToA()
+	})
+}
+
+// Suppress suppresses specific error messages using regular expressions.
+func (a Action) Suppress(expr ...string) Action {
 	return ActionCallback(func(c Context) Action {
 		invoked := a.Invoke(c)
-		for index, v := range invoked.rawValues {
-			invoked.rawValues[index].Style = f(v.Value, c)
+		if err := invoked.meta.Messages.Suppress(expr...); err != nil {
+			return ActionMessage(err.Error())
 		}
 		return invoked.ToA()
 	})
@@ -161,73 +222,6 @@ func (a Action) TagF(f func(s string) string) Action {
 	})
 }
 
-// Chdir changes the current working directory to the named directory for the duration of invocation.
-func (a Action) Chdir(dir string) Action {
-	return ActionCallback(func(c Context) Action {
-		abs, err := c.Abs(dir)
-		if err != nil {
-			return ActionMessage(err.Error())
-		}
-		if info, err := os.Stat(abs); err != nil {
-			return ActionMessage(err.Error())
-		} else if !info.IsDir() {
-			return ActionMessage("not a directory: %v", abs)
-		}
-		c.Dir = abs
-		return a.Invoke(c).ToA()
-	})
-}
-
-// Suppress suppresses specific error messages using regular expressions.
-func (a Action) Suppress(expr ...string) Action {
-	return ActionCallback(func(c Context) Action {
-		invoked := a.Invoke(c)
-		if err := invoked.meta.Messages.Suppress(expr...); err != nil {
-			return ActionMessage(err.Error())
-		}
-		return invoked.ToA()
-	})
-}
-
-// MultiParts splits values of an Action by given dividers and completes each segment separately.
-func (a Action) MultiParts(dividers ...string) Action {
-	return ActionCallback(func(c Context) Action {
-		return a.Invoke(c).ToMultiPartsA(dividers...)
-	})
-}
-
-// List wraps the Action in an ActionMultiParts with given divider.
-func (a Action) List(divider string) Action {
-	return ActionMultiParts(divider, func(c Context) Action {
-		return a.Invoke(c).ToA().NoSpace()
-	})
-}
-
-// UniqueList wraps the Action in an ActionMultiParts with given divider.
-func (a Action) UniqueList(divider string) Action {
-	return ActionMultiParts(divider, func(c Context) Action {
-		return a.Invoke(c).Filter(c.Parts).ToA().NoSpace()
-	})
-}
-
-// Prefix adds a prefix to values (only the ones inserted, not the display values).
-//
-//	carapace.ActionValues("melon", "drop", "fall").Prefix("water")
-func (a Action) Prefix(prefix string) Action {
-	return ActionCallback(func(c Context) Action {
-		return a.Invoke(c).Prefix(prefix).ToA()
-	})
-}
-
-// Suffix adds a suffx to values (only the ones inserted, not the display values).
-//
-//	carapace.ActionValues("apple", "melon", "orange").Suffix("juice")
-func (a Action) Suffix(suffix string) Action {
-	return ActionCallback(func(c Context) Action {
-		return a.Invoke(c).Suffix(suffix).ToA()
-	})
-}
-
 // Timeout sets the maximum duration an Action may take to invoke.
 //
 //	carapace.ActionCallback(func(c carapace.Context) carapace.Action {
@@ -252,3 +246,27 @@ func (a Action) Timeout(d time.Duration, alternative Action) Action {
 		return result.ToA()
 	})
 }
+
+// UniqueList wraps the Action in an ActionMultiParts with given divider.
+func (a Action) UniqueList(divider string) Action {
+	return ActionMultiParts(divider, func(c Context) Action {
+		return a.Invoke(c).Filter(c.Parts).ToA().NoSpace()
+	})
+}
+
+// Usage sets the usage.
+func (a Action) Usage(usage string, args ...interface{}) Action {
+	return a.UsageF(func() string {
+		return fmt.Sprintf(usage, args...)
+	})
+}
+
+// Usage sets the usage using a function.
+func (a Action) UsageF(f func() string) Action {
+	return ActionCallback(func(c Context) Action {
+		if usage := f(); usage != "" {
+			a.meta.Usage = usage
+		}
+		return a
+	})
+}
diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md
index 1272e5f..efc1bbf 100644
--- a/docs/src/SUMMARY.md
+++ b/docs/src/SUMMARY.md
@@ -9,11 +9,13 @@
   - [Action](./carapace/action.md)
     - [Cache](./carapace/action/cache.md)
     - [Chdir](./carapace/action/chdir.md)
+    - [Filter](./carapace/action/filter.md)
     - [Invoke](./carapace/action/invoke.md)
     - [List](./carapace/action/list.md)
     - [MultiParts](./carapace/action/multiParts.md)
     - [NoSpace](./carapace/action/noSpace.md)
     - [Prefix](./carapace/action/prefix.md)
+    - [Retain](./carapace/action/retain.md)
     - [Style](./carapace/action/style.md)
     - [StyleF](./carapace/action/styleF.md)
     - [StyleR](./carapace/action/styleR.md)
diff --git a/docs/src/carapace/action/filter.cast b/docs/src/carapace/action/filter.cast
new file mode 100644
index 0000000..6280b77
--- /dev/null
+++ b/docs/src/carapace/action/filter.cast
@@ -0,0 +1,41 @@
+{"version": 2, "width": 108, "height": 24, "timestamp": 1689158161, "env": {"SHELL": "elvish", "TERM": "tmux-256color"}}
+[0.065171, "o", "\u001b[?7h\u001b[7m⏎\u001b[m                                                                                                           \r \r\u001b[?7l\u001b[?2004h"]
+[0.065792, "o", "\u001b[?25l\r???>                                                                                                   ???> \r\u001b[5C\u001b[?25h\u001b[?25l\r\u001b[5C\u001b[K\r\u001b[5C\u001b[?25h"]
+[0.074083, "o", "\u001b[?25l\r\r\u001b[5C\u001b[?25h"]
+[0.074275, "o", "\u001b[?25l\r\u001b[K\r\n\u001b[0;1;36mcarapace/example\u001b[0;m on \u001b[0;1;35m action-retain\u001b[0;m \u001b[0;1;31m[$!?]\u001b[0;m via \u001b[0;1;36m🐹 v1.20.5 \r\n\u001b[0;1;37mesh\u001b[0;m \u001b[0;1;32m❯\u001b[0;m \r\u001b[6C\u001b[?25h"]
+[0.587118, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;31me\u001b[0;m\r\u001b[7C\u001b[?25h"]
+[0.587557, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.600157, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.600276, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.769978, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[7C\u001b[0;31mx\u001b[0;m\r\u001b[8C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[0.906975, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexa\u001b[0;m\r\u001b[9C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[1.008014, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;31mexam\u001b[0;m\r\u001b[10C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h"]
+[1.06184, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[10C\u001b[0;31mp\u001b[0;m\r\u001b[11C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[11C\u001b[?25h"]
+[1.198941, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[11C\u001b[0;31ml\u001b[0;m\r\u001b[12C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[12C\u001b[?25h"]
+[1.263944, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexample\u001b[0;m\r\u001b[13C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[13C\u001b[?25h"]
+[1.328305, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[13C \r\u001b[14C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[14C\u001b[?25h"]
+[1.48774, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[14Cm\r\u001b[15C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[15C\u001b[?25h"]
+[1.573788, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[15Co\r\u001b[16C\u001b[?25h"]
+[1.573866, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[16C\u001b[?25h"]
+[1.575372, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[16C\u001b[?25h"]
+[1.575431, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[16C\u001b[?25h"]
+[1.694119, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[16Cdifier \r\u001b[23C\u001b[?25h"]
+[2.106809, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[23C-\r\u001b[24C\u001b[?25h"]
+[2.106893, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[24C\u001b[?25h"]
+[2.262227, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[24C-\r\u001b[25C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[25C\u001b[?25h"]
+[2.516307, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[25Cf\r\u001b[26C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[26C\u001b[?25h"]
+[2.642845, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[26Ci\r\u001b[27C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[27C\u001b[?25h"]
+[2.824332, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[27Clter \r\u001b[32C\u001b[?25h"]
+[3.289998, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[32C\u001b[0;4m1 \r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7m1\u001b[0;2;7m (one)\u001b[0;m  3\u001b[0;2m (three)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[4.228643, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[32C\u001b[K\u001b[0;4m3 \r\n\r\n\u001b[0;m\u001b[K1\u001b[0;2m (one)\u001b[0;m  \u001b[0;7m3\u001b[0;2;7m (three)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[5.733904, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[6C\u001b[K\r\n\u001b[J\u001b[A\r\u001b[6C\u001b[?25h"]
+[5.734661, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.752609, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.752756, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.969899, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;31me\u001b[0;m\r\u001b[7C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[6.212241, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[7C\u001b[0;31mx\u001b[0;m\r\u001b[8C\u001b[?25h"]
+[6.212335, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[6.3745, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[8C\u001b[0;31mi\u001b[0;m\r\u001b[9C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[6.445559, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexit\u001b[0;m\r\u001b[10C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h"]
+[6.624242, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\n\r\u001b[?25h"]
+[6.624648, "o", "\u001b[?7h\u001b[?2004l\r"]
diff --git a/docs/src/carapace/action/filter.md b/docs/src/carapace/action/filter.md
new file mode 100644
index 0000000..394e4ea
--- /dev/null
+++ b/docs/src/carapace/action/filter.md
@@ -0,0 +1,16 @@
+# Filter
+
+[`Filter`] filters given values.
+
+```go
+carapace.ActionValuesDescribed(
+	"1", "one",
+	"2", "two",
+	"3", "three",
+	"4", "four",
+).Filter([]string{"2", "4"})
+```
+
+![](./filter.cast)
+
+[`Filter`]: https://pkg.go.dev/github.com/rsteube/carapace#Action.Filter
diff --git a/docs/src/carapace/action/retain.cast b/docs/src/carapace/action/retain.cast
new file mode 100644
index 0000000..c0db79c
--- /dev/null
+++ b/docs/src/carapace/action/retain.cast
@@ -0,0 +1,41 @@
+{"version": 2, "width": 108, "height": 24, "timestamp": 1689158181, "env": {"SHELL": "elvish", "TERM": "tmux-256color"}}
+[0.070882, "o", "\u001b[?7h\u001b[7m⏎\u001b[m                                                                                                           \r \r\u001b[?7l\u001b[?2004h"]
+[0.071423, "o", "\u001b[?25l\r???>                                                                                                   ???> \r\u001b[5C\u001b[?25h\u001b[?25l\r\u001b[5C\u001b[K\r\u001b[5C\u001b[?25h"]
+[0.083515, "o", "\u001b[?25l\r\r\u001b[5C\u001b[?25h"]
+[0.083754, "o", "\u001b[?25l\r\u001b[K\r\n\u001b[0;1;36mcarapace/example\u001b[0;m on \u001b[0;1;35m action-retain\u001b[0;m \u001b[0;1;31m[$!?]\u001b[0;m via \u001b[0;1;36m🐹 v1.20.5 \r\n\u001b[0;1;37mesh\u001b[0;m \u001b[0;1;32m❯\u001b[0;m \r\u001b[6C\u001b[?25h"]
+[0.412814, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;31me\u001b[0;m\r\u001b[7C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.41334, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.423245, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[0.588142, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[7C\u001b[0;31mx\u001b[0;m\r\u001b[8C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[0.735276, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexa\u001b[0;m\r\u001b[9C\u001b[?25h"]
+[0.735347, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[0.837106, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;31mexam\u001b[0;m\r\u001b[10C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h"]
+[0.876364, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[10C\u001b[0;31mp\u001b[0;m\r\u001b[11C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[11C\u001b[?25h"]
+[1.038023, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[11C\u001b[0;31ml\u001b[0;m\r\u001b[12C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[12C\u001b[?25h"]
+[1.106702, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexample\u001b[0;m\r\u001b[13C\u001b[?25h"]
+[1.106807, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[13C\u001b[?25h"]
+[1.204027, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[13C \r\u001b[14C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[14C\u001b[?25h"]
+[1.343381, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[14Cm\r\u001b[15C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[15C\u001b[?25h"]
+[1.410835, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[15Co\r\u001b[16C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[16C\u001b[?25h"]
+[1.551868, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[16Cdifier \r\u001b[23C\u001b[?25h"]
+[1.919688, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[23C-\r\u001b[24C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[24C\u001b[?25h"]
+[2.053776, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[24C-\r\u001b[25C\u001b[?25h"]
+[2.053872, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[25C\u001b[?25h"]
+[2.378561, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[25Cr\r\u001b[26C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[26C\u001b[?25h"]
+[2.466607, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[26Ce\r\u001b[27C\u001b[?25h"]
+[2.466684, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[27C\u001b[?25h"]
+[2.656245, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[27Ctain \r\u001b[32C\u001b[?25h"]
+[3.133397, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[32C\u001b[0;4m2 \r\n\u001b[0;1;37;45m COMPLETING argument \u001b[0;m \r\n\u001b[0;7m2\u001b[0;2;7m (two)\u001b[0;m  4\u001b[0;2m (four)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[3.774198, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[32C\u001b[K\u001b[0;4m4 \r\n\r\n\u001b[0;m\u001b[K2\u001b[0;2m (two)\u001b[0;m  \u001b[0;7m4\u001b[0;2;7m (four)\u001b[0;m\u001b[1A\r\u001b[22C\u001b[?25h"]
+[5.468723, "o", "\u001b[?25l\u001b[3A\r\r\n\r\n\u001b[6C\u001b[K\r\n\u001b[J\u001b[A\r\u001b[6C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.469673, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.486014, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.48608, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[6C\u001b[?25h"]
+[5.740105, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[0;31me\u001b[0;m\r\u001b[7C\u001b[?25h"]
+[5.740396, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[7C\u001b[?25h"]
+[5.943355, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[7C\u001b[0;31mx\u001b[0;m\r\u001b[8C\u001b[?25h"]
+[5.94357, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[8C\u001b[?25h"]
+[6.054215, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[8C\u001b[0;31mi\u001b[0;m\r\u001b[9C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[9C\u001b[?25h"]
+[6.180112, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\u001b[6C\u001b[K\u001b[0;32mexit\u001b[0;m\r\u001b[10C\u001b[?25h"]
+[6.180364, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h\u001b[?25l\u001b[2A\r\r\n\r\n\r\u001b[10C\u001b[?25h"]
+[6.333115, "o", "\u001b[?25l\u001b[2A\r\r\n\r\n\r\n\r\u001b[?25h\u001b[?7h\u001b[?2004l\r"]
diff --git a/docs/src/carapace/action/retain.md b/docs/src/carapace/action/retain.md
new file mode 100644
index 0000000..2580d6a
--- /dev/null
+++ b/docs/src/carapace/action/retain.md
@@ -0,0 +1,16 @@
+# Retain
+
+[`Retain`] retains given values.
+
+```go
+carapace.ActionValuesDescribed(
+	"1", "one",
+	"2", "two",
+	"3", "three",
+	"4", "four",
+).Retain([]string{"2", "4"})
+```
+
+![](./retain.cast)
+
+[`Retain`]: https://pkg.go.dev/github.com/rsteube/carapace#Action.Retain
diff --git a/example/cmd/modifier.go b/example/cmd/modifier.go
index 547be49..fb24673 100644
--- a/example/cmd/modifier.go
+++ b/example/cmd/modifier.go
@@ -25,11 +25,13 @@ func init() {
 	modifierCmd.Flags().String("cache", "", "Cache()")
 	modifierCmd.Flags().String("cache-key", "", "Cache()")
 	modifierCmd.Flags().String("chdir", "", "Chdir()")
+	modifierCmd.Flags().String("filter", "", "Filter()")
 	modifierCmd.Flags().String("invoke", "", "Invoke()")
 	modifierCmd.Flags().String("list", "", "List()")
 	modifierCmd.Flags().String("multiparts", "", "MultiParts()")
 	modifierCmd.Flags().String("nospace", "", "NoSpace()")
 	modifierCmd.Flags().String("prefix", "", "Prefix()")
+	modifierCmd.Flags().String("retain", "", "Retain()")
 	modifierCmd.Flags().String("style", "", "Style()")
 	modifierCmd.Flags().String("stylef", "", "StyleF()")
 	modifierCmd.Flags().String("styler", "", "StyleR()")
@@ -76,7 +78,13 @@ func init() {
 			}
 		}),
 		"chdir": carapace.ActionFiles().Chdir(os.TempDir()),
-		"list":  carapace.ActionValues("one", "two", "three").List(","),
+		"filter": carapace.ActionValuesDescribed(
+			"1", "one",
+			"2", "two",
+			"3", "three",
+			"4", "four",
+		).Filter([]string{"2", "4"}),
+		"list": carapace.ActionValues("one", "two", "three").List(","),
 		"invoke": carapace.ActionCallback(func(c carapace.Context) carapace.Action {
 			if !strings.HasPrefix(c.Value, "file://") {
 				return carapace.ActionValues("file://").NoSpace()
@@ -121,6 +129,12 @@ func init() {
 			"drop",
 			"fall",
 		).Prefix("water"),
+		"retain": carapace.ActionValuesDescribed(
+			"1", "one",
+			"2", "two",
+			"3", "three",
+			"4", "four",
+		).Retain([]string{"2", "4"}),
 		"style": carapace.ActionValues(
 			"one",
 			"two",
diff --git a/internal/common/value.go b/internal/common/value.go
index a6b11aa..a67d714 100644
--- a/internal/common/value.go
+++ b/internal/common/value.go
@@ -82,6 +82,21 @@ func (r RawValues) Filter(values ...string) RawValues {
 	return filtered
 }
 
+// Retain retains given values.
+func (r RawValues) Retain(values ...string) RawValues {
+	toretain := make(map[string]bool)
+	for _, v := range values {
+		toretain[v] = true
+	}
+	filtered := make([]RawValue, 0)
+	for _, rawValue := range r {
+		if _, ok := toretain[rawValue.Value]; ok {
+			filtered = append(filtered, rawValue)
+		}
+	}
+	return filtered
+}
+
 // Decolor clears style for all values.
 func (r RawValues) Decolor() RawValues {
 	rawValues := make(RawValues, len(r))
diff --git a/invokedAction.go b/invokedAction.go
index 0b1d603..8b84d93 100644
--- a/invokedAction.go
+++ b/invokedAction.go
@@ -17,7 +17,7 @@ func (a InvokedAction) export() export.Export {
 	return export.Export{Meta: a.meta, Values: a.rawValues}
 }
 
-// Filter filters given values (this should be done before any call to Prefix/Suffix as those alter the values being filtered)
+// Filter filters given values.
 //
 //	a := carapace.ActionValues("A", "B", "C").Invoke(c)
 //	b := a.Filter([]string{"B"}) // ["A", "C"]
@@ -50,6 +50,15 @@ func (a InvokedAction) Prefix(prefix string) InvokedAction {
 	return a
 }
 
+// Retain retains given values.
+//
+//	a := carapace.ActionValues("A", "B", "C").Invoke(c)
+//	b := a.Retain([]string{"A", "C"}) // ["A", "C"]
+func (a InvokedAction) Retain(values []string) InvokedAction {
+	a.rawValues = a.rawValues.Retain(values...)
+	return a
+}
+
 // Suffix adds a suffx to values (only the ones inserted, not the display values)
 //
 //	carapace.ActionValues("apple", "melon", "orange").Invoke(c).Suffix("juice")


Test patch:
diff --git a/example/cmd/modifier_test.go b/example/cmd/modifier_test.go
index d462273..7539969 100644
--- a/example/cmd/modifier_test.go
+++ b/example/cmd/modifier_test.go
@@ -21,6 +21,26 @@ func TestBatch(t *testing.T) {
 	})
 }
 
+func TestFilter(t *testing.T) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+		s.Run("modifier", "--filter", "").
+			Expect(carapace.ActionValuesDescribed(
+				"1", "one",
+				"3", "three",
+			).Usage("Filter()"))
+	})
+}
+
+func TestRetain(t *testing.T) {
+	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
+		s.Run("modifier", "--retain", "").
+			Expect(carapace.ActionValuesDescribed(
+				"2", "two",
+				"4", "four",
+			).Usage("Retain()"))
+	})
+}
+
 func TestTimeout(t *testing.T) {
 	sandbox.Package(t, "github.com/rsteube/carapace/example")(func(s *sandbox.Sandbox) {
 		s.Run("modifier", "--timeout", "1s:").