diff --git a/pom.xml b/pom.xml
index cad65101b..ccb5606b8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,7 @@
3.21.0
1.81
4.3.1
+ 1.2
@@ -110,6 +111,11 @@
ST4
${antlr-st4.version}
+
+ com.google.jimfs
+ jimfs
+ ${jimfs.version}
+
diff --git a/spring-shell-core/pom.xml b/spring-shell-core/pom.xml
index ce87eba41..ddd52cc1c 100644
--- a/spring-shell-core/pom.xml
+++ b/spring-shell-core/pom.xml
@@ -52,6 +52,15 @@
assertj-core
test
-
+
+ org.awaitility
+ awaitility
+ test
+
+
+ com.google.jimfs
+ jimfs
+ test
+
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java b/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java
new file mode 100644
index 000000000..791475448
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.shell.component;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.jline.terminal.Terminal;
+import org.jline.utils.AttributedString;
+
+import org.springframework.shell.component.context.ComponentContext;
+import org.springframework.shell.component.support.AbstractSelectorComponent;
+import org.springframework.shell.component.support.Enableable;
+import org.springframework.shell.component.support.Itemable;
+import org.springframework.shell.component.support.Matchable;
+import org.springframework.shell.component.support.Nameable;
+import org.springframework.shell.component.support.AbstractSelectorComponent.SelectorComponentContext;
+import org.springframework.shell.component.MultiItemSelector.MultiItemSelectorContext;
+
+/**
+ * Component able to pick multiple items.
+ *
+ * @author Janne Valkealahti
+ */
+public class MultiItemSelector>
+ extends AbstractSelectorComponent, I> {
+
+ private MultiItemSelectorContext currentContext;
+
+ public MultiItemSelector(Terminal terminal, List items, String name, Comparator comparator) {
+ super(terminal, name, items, false, comparator);
+ setRenderer(new DefaultRenderer());
+ setTemplateLocation("classpath:org/springframework/shell/component/multi-item-selector-default.stg");
+ }
+
+ @Override
+ protected MultiItemSelectorContext getThisContext(ComponentContext> context) {
+ if (context != null && currentContext == context) {
+ return currentContext;
+ }
+ currentContext = MultiItemSelectorContext.empty(getItemMapper());
+ currentContext.setName(name);
+ if (currentContext.getItems() == null) {
+ currentContext.setItems(getItems());
+ }
+ context.stream().forEach(e -> {
+ currentContext.put(e.getKey(), e.getValue());
+ });
+ return currentContext;
+ }
+
+ @Override
+ protected MultiItemSelectorContext runInternal(MultiItemSelectorContext context) {
+ super.runInternal(context);
+ loop(context);
+ return context;
+ }
+
+ /**
+ * Context {@link MultiItemSelector}.
+ */
+ public interface MultiItemSelectorContext>
+ extends SelectorComponentContext> {
+
+ /**
+ * Gets a values.
+ *
+ * @return a values
+ */
+ List getValues();
+
+ /**
+ * Creates an empty {@link MultiItemSelectorContext}.
+ *
+ * @return empty context
+ */
+ static > MultiItemSelectorContext empty() {
+ return new DefaultMultiItemSelectorContext<>();
+ }
+
+ /**
+ * Creates an {@link MultiItemSelectorContext}.
+ *
+ * @return context
+ */
+ static > MultiItemSelectorContext empty(Function itemMapper) {
+ return new DefaultMultiItemSelectorContext<>(itemMapper);
+ }
+ }
+
+ private static class DefaultMultiItemSelectorContext> extends
+ BaseSelectorComponentContext> implements MultiItemSelectorContext {
+
+ private Function itemMapper = item -> item.toString();
+
+ DefaultMultiItemSelectorContext() {
+ }
+
+ DefaultMultiItemSelectorContext(Function itemMapper) {
+ this.itemMapper = itemMapper;
+ }
+
+ @Override
+ public List getValues() {
+ if (getResultItems() == null) {
+ return Collections.emptyList();
+ }
+ return getResultItems().stream()
+ .map(i -> i.getItem())
+ .map(i -> itemMapper.apply(i))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public Map toTemplateModel() {
+ Map attributes = super.toTemplateModel();
+ attributes.put("values", getValues());
+ List