Repository: ktr0731/evans (go)

Issue:
google.protobuf.Struct not working with list_value
When digging down a google.protobuf.Struct and using the list_value type as value evans doesn't dig down to the value selection of the list type. When reaching `circulated field was found` and then hitting `dig down` it will not show the type selection but instantly jump to the next `circulated field was found` entry.
```
<repeated> filter::dimension::fields::key (TYPE_STRING) => size
✔ dig down
✔ list_value
✔ dig down
✔ dig down
✔ dig down
✔ dig down
✔ dig down
✔ dig down
Use the arrow keys to navigate: ↓ ↑ → ← 
? circulated field was found. dig down or finish? field: filter::dimension::fields::value::list_value::values (): 
  ▸ dig down
    finish
```
My proto file section for the struct looks like the following.
```
google.protobuf.Struct dimension = 6;
```

Pull request:
rewrite InteractiveFiller
This change applies the new InteractiveFiller to REPL mode. 
The new one fixes some bugs and it is a more efficient implementation.

`--add-repeated-manually`, a new option is also introduced. A prompt will be displayed when the resolver encountered a repeated field and the flag is true. This option is useful when inputting a repeated field that has a repeated field indirectly. This option fixes #378.

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 1daa2f7..d11c23c 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -30,14 +30,11 @@ linters:
     - unused
     - varcheck
     - whitespace
-    - interfacer
-    - scopelint
     - asciicheck
     - depguard
     - godot
     - gomodguard
     - goprintffuncname
-    - maligned
     - nolintlint
     - rowserrcheck
 
@@ -47,5 +44,4 @@ issues:
   exclude-rules:
     - path: _test\.go
       linters:
-        - interfacer
         - maligned
diff --git a/app/update.go b/app/update.go
index ef70447..b087335 100644
--- a/app/update.go
+++ b/app/update.go
@@ -120,7 +120,7 @@ func processUpdate(ctx context.Context, cfg *config.Config, w io.Writer, c *cach
 
 	printUpdateInfo(w, c.UpdateInfo.LatestVersion)
 
-	selected, err := prompt.Select("update?", []string{"yes", "no"})
+	_, selected, err := prompt.Select("update?", []string{"yes", "no"})
 	if err != nil || selected == "no" {
 		// Abort updating.
 		return nil
diff --git a/fill/filler.go b/fill/filler.go
index 51529bb..99ac2b2 100644
--- a/fill/filler.go
+++ b/fill/filler.go
@@ -18,11 +18,15 @@ type Filler interface {
 	Fill(v interface{}) error
 }
 
-// InteractiveFillerOpts
-// If DigManually is true, Fill asks whether to dig down if it encountered to a message field.
-// If BytesFromFile is true, Fill will read the contents of the file from the provided relative path.
+// InteractiveFillerOpts represents options for InteractiveFiller.
 type InteractiveFillerOpts struct {
-	DigManually, BytesFromFile bool
+	// DigManually is true, Fill asks whether to dig down if it encountered to a message field.
+	DigManually,
+	// BytesFromFile is true, Fill will read the contents of the file from the provided relative path.
+	BytesFromFile,
+	// AddRepeatedManually is true, Fill asks whether to add a repeated field value
+	// if it encountered to a repeated field.
+	AddRepeatedManually bool
 }
 
 // Filler tries to correspond input text to a struct interactively.
diff --git a/fill/proto/interactive_filler.go b/fill/proto/interactive_filler.go
index 1becc83..cec6775 100644
--- a/fill/proto/interactive_filler.go
+++ b/fill/proto/interactive_filler.go
@@ -4,14 +4,16 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"strconv"
 	"strings"
 
-	"github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"github.com/jhump/protoreflect/desc"
+	"github.com/jhump/protoreflect/desc/builder"
 	"github.com/jhump/protoreflect/dynamic"
 	"github.com/ktr0731/evans/fill"
 	"github.com/ktr0731/evans/prompt"
 	"github.com/pkg/errors"
+	"google.golang.org/protobuf/types/descriptorpb"
 )
 
 // InteractiveFiller is an implementation of fill.InteractiveFiller.
@@ -19,10 +21,6 @@ import (
 type InteractiveFiller struct {
 	prompt       prompt.Prompt
 	prefixFormat string
-	state        promptInputterState
-
-	digManually   bool
-	bytesFromFile bool
 }
 
 // NewInteractiveFiller instantiates a new filler that fills each field interactively.
@@ -38,434 +36,329 @@ func NewInteractiveFiller(prompt prompt.Prompt, prefixFormat string) *Interactiv
 //
 // Note that Fill resets the previous state when it is called again.
 func (f *InteractiveFiller) Fill(v interface{}, opts fill.InteractiveFillerOpts) error {
-	f.digManually = opts.DigManually
-	f.bytesFromFile = opts.BytesFromFile
-
 	msg, ok := v.(*dynamic.Message)
 	if !ok {
 		return fill.ErrCodecMismatch
 	}
 
-	f.state = initialPromptInputterState.clone()
-	err := f.inputMessage(msg)
-	// If io.EOF is returned, it means CTRL+d is entered.
-	// In this case, Input skips rest fields and finishes normally.
-	if errors.Is(err, io.EOF) {
-		return io.EOF
-	}
-	if errors.Is(err, prompt.ErrAbort) {
-		return nil
-	}
+	resolver := newResolver(f.prompt, f.prefixFormat, prompt.ColorInitial, msg, nil, false, opts)
+	_, err := resolver.resolve()
 	if err != nil {
 		return err
 	}
+
 	return nil
 }
 
-// inputMessage might call itself for nested messages.
-//
-// e.g.
-//  message Foo {
-//    Bar bar = 1;
-//    string baz = 2;
-//  }
-//
-// inputMessage is called two times. One for Foo's primitive fields, and another one for bar's primitive fields.
-//
-// inputMessage returns following errors:
-//
-//   - io.EOF:
-//       CTRL+d entered. Never return in the case of repeated message.
-//       inputMessage also returns the first return value what is the message partially inputted.
-//
-func (f *InteractiveFiller) inputMessage(dmsg *dynamic.Message) error {
-	f.prompt.SetPrefixColor(f.state.color)
-
-	// f.state is message-scoped, so we reset f.state after inputting msg.
-	currentState := f.state.clone()
-	defer func() {
-		f.state = currentState
-	}()
-
-	for _, field := range dmsg.GetMessageDescriptor().GetFields() {
-		err := f.inputField(dmsg, field, false)
-		if errors.Is(err, io.EOF) {
-			return io.EOF
-		}
-		if err != nil {
-			return errors.Wrapf(err, "failed to set inputted values to message '%s'", dmsg.GetMessageDescriptor().GetFullyQualifiedName())
-		}
-	}
+type resolver struct {
+	prompt       prompt.Prompt
+	prefixFormat string
+	color        prompt.Color
 
-	return nil
-}
+	msg *dynamic.Message
 
-// inputField tries to set a inputted value to a field of the passed message dmsg.
-// An argument partOfRepeatedField means inputField is called from inputRepeatedField.
-//
-// inputField returns following errors:
-//   - io.EOF: CTRL+d is entered.
-func (f *InteractiveFiller) inputField(dmsg *dynamic.Message, field *desc.FieldDescriptor, partOfRepeatedField bool) error {
-	// If a repeated field is found, call inputRepeatedField instead.
-	if !partOfRepeatedField && field.IsRepeated() {
-		return f.inputRepeatedField(dmsg, field)
-	}
+	m         *desc.MessageDescriptor
+	ancestors []string
+	// repeated represents that the message is repeated field or not.
+	// If the message is not a field or not a repeated field, it is false.
+	repeated bool
 
-	if isOneOfField(field) {
-		if f.isSelectedOneOf(field) {
-			return nil
-		}
-		var err error
-		field, err = f.selectOneOf(field)
-		if err != nil {
-			return err
-		}
-		f.state.selectedOneOf[field.GetOneOf().GetFullyQualifiedName()] = nil
+	opts fill.InteractiveFillerOpts
+}
+
+func newResolver(
+	prompt prompt.Prompt,
+	prefixFormat string,
+	color prompt.Color,
+	msg *dynamic.Message,
+	ancestors []string,
+	repeated bool,
+	opts fill.InteractiveFillerOpts,
+) *resolver {
+	return &resolver{
+		prompt:       prompt,
+		prefixFormat: prefixFormat,
+		color:        color,
+		msg:          msg,
+		m:            msg.GetMessageDescriptor(),
+		ancestors:    ancestors,
+		repeated:     repeated,
+		opts:         opts,
 	}
+}
 
-	old := f.state.hasAncestorAndHasRepeatedField
-	f.state.hasAncestorAndHasRepeatedField = f.state.hasAncestorAndHasRepeatedField || field.IsRepeated()
-	defer func() {
-		f.state.hasAncestorAndHasRepeatedField = old
-	}()
+func (r *resolver) resolve() (*dynamic.Message, error) {
+	selectedOneof := make(map[string]interface{})
 
-	switch {
-	case field.GetEnumType() != nil:
-		v, err := f.selectEnum(field)
-		if err != nil {
-			return err
-		}
-		if partOfRepeatedField {
-			if err := dmsg.TryAddRepeatedField(field, v.GetNumber()); err != nil {
-				return err
-			}
-		} else {
-			if err := dmsg.TrySetField(field, v.GetNumber()); err != nil {
-				return err
-			}
-		}
-	case field.GetMessageType() != nil:
-		if f.isCirculatedField(field) {
-			prefix := strings.Join(f.state.ancestor, ancestorDelimiter)
-			if prefix != "" {
-				prefix += ancestorDelimiter
-			}
-			prefix += field.GetName()
-
-			choice, err := f.prompt.Select(
-				fmt.Sprintf("circulated field was found. dig down or finish? field: %s (%s)", prefix, strings.Join(f.state.circulatedMessages[field.GetMessageType().GetFullyQualifiedName()], ">")),
-				[]string{"dig down", "finish"},
-			)
-			if err != nil {
-				return err
-			}
-			if choice == "finish" {
-				if field.IsRepeated() {
-					return io.EOF
-				}
-				return nil
+	for _, f := range r.m.GetFields() {
+		if isOneOfField := f.GetOneOf() != nil; isOneOfField {
+			fqn := f.GetOneOf().GetFullyQualifiedName()
+			if _, selected := selectedOneof[fqn]; selected {
+				// Skip if one of choices is already selected.
+				continue
 			}
-		}
 
-		ancestorLen := len(f.state.ancestor)
-		f.state.ancestor = append(f.state.ancestor, field.GetName())
-
-		if f.digManually {
-			choice, err := f.prompt.Select(
-				fmt.Sprintf(
-					"dig down? field: %s, message: %s",
-					field.GetFullyQualifiedName(),
-					dmsg.GetMessageDescriptor().GetFullyQualifiedName(),
-				),
-				[]string{"dig down", "skip"},
-			)
-			if err != nil {
-				return err
-			}
-			if choice == "skip" {
-				return nil
+			selectedOneof[fqn] = nil
+			if err := r.resolveOneof(f.GetOneOf()); err != nil {
+				return nil, err
 			}
+			continue
 		}
 
-		msg := dynamic.NewMessage(field.GetMessageType())
-		err := f.inputMessage(msg)
-		// If io.EOF is returned, msg isn't nil (see inputMessage comments).
-		if err != nil && !errors.Is(err, prompt.ErrAbort) {
-			return err
+		err := r.resolveField(f)
+		if errors.Is(err, prompt.ErrAbort) {
+			return r.msg, nil
 		}
-		if errors.Is(err, io.EOF) {
-			return io.EOF
+		if err != nil {
+			return nil, err
 		}
+	}
 
-		if partOfRepeatedField {
-			if err := dmsg.TryAddRepeatedField(field, msg); err != nil {
-				return errors.Wrap(err, "failed to add an inputted message to a repeated field")
-			}
-		} else {
-			if err := dmsg.TrySetField(field, msg); err != nil {
-				return errors.Wrap(err, "failed to set an inputted message to a field")
-			}
-		}
+	return r.msg, nil
+}
 
-		if partOfRepeatedField && errors.Is(err, prompt.ErrAbort) {
-			return prompt.ErrAbort
-		}
+func (r *resolver) resolveOneof(o *desc.OneOfDescriptor) error {
+	choices := make([]string, 0, len(o.GetChoices()))
+	for _, c := range o.GetChoices() {
+		choices = append(choices, c.GetName())
+	}
 
-		// Discard appended ancestors after calling above inputMessage.
-		f.state.ancestor = f.state.ancestor[:ancestorLen]
-		f.state.color.Next()
-	default: // Normal fields.
-		f.prompt.SetPrefix(f.makePrefix(field))
-		v, err := f.inputPrimitiveField(field.GetType())
-		if err != nil {
-			return err
-		}
+	choice, err := r.selectChoices(o.GetFullyQualifiedName(), choices)
+	if err != nil {
+		return err
+	}
+
+	return r.resolveField(o.GetChoices()[choice])
+}
+
+func (r *resolver) resolveField(f *desc.FieldDescriptor) error {
+	resolve := func(f *desc.FieldDescriptor) (interface{}, error) {
+		var converter func(string) (interface{}, error)
 
-		if partOfRepeatedField {
-			if err := dmsg.TryAddRepeatedField(field, v); err != nil {
-				return errors.Wrapf(err, "failed to add inputted value to repeated field '%s'", field.GetName())
+		switch t := f.GetType(); t {
+		case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
+			if r.skipMessage(f) {
+				return nil, prompt.ErrAbort
 			}
-		} else {
-			if err := dmsg.TrySetField(field, v); err != nil {
-				return errors.Wrapf(err, "failed to set inputted value to field '%s'", field.GetName())
+
+			msgr := newResolver(
+				r.prompt,
+				r.prefixFormat,
+				r.color.NextVal(),
+				dynamic.NewMessage(f.GetMessageType()),
+				append(r.ancestors, f.GetName()),
+				r.repeated || f.IsRepeated(),
+				r.opts,
+			)
+			return msgr.resolve()
+		case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
+			return r.resolveEnum(f.GetEnumType())
+		case descriptorpb.FieldDescriptorProto_TYPE_DOUBLE:
+			converter = func(v string) (interface{}, error) { return strconv.ParseFloat(v, 64) }
+
+		case descriptorpb.FieldDescriptorProto_TYPE_FLOAT:
+			converter = func(v string) (interface{}, error) {
+				f, err := strconv.ParseFloat(v, 32)
+				return float32(f), err
 			}
-		}
-	}
 
-	return nil
-}
+		case descriptorpb.FieldDescriptorProto_TYPE_INT64,
+			descriptorpb.FieldDescriptorProto_TYPE_SFIXED64,
+			descriptorpb.FieldDescriptorProto_TYPE_SINT64:
+			converter = func(v string) (interface{}, error) { return strconv.ParseInt(v, 10, 64) }
+
+		case descriptorpb.FieldDescriptorProto_TYPE_UINT64,
+			descriptorpb.FieldDescriptorProto_TYPE_FIXED64:
+			converter = func(v string) (interface{}, error) { return strconv.ParseUint(v, 10, 64) }
+
+		case descriptorpb.FieldDescriptorProto_TYPE_INT32,
+			descriptorpb.FieldDescriptorProto_TYPE_SFIXED32,
+			descriptorpb.FieldDescriptorProto_TYPE_SINT32:
+			converter = func(v string) (interface{}, error) {
+				i, err := strconv.ParseInt(v, 10, 32)
+				return int32(i), err
+			}
 
-func (f *InteractiveFiller) selectOneOf(field *desc.FieldDescriptor) (*desc.FieldDescriptor, error) {
-	oneof := field.GetOneOf()
+		case descriptorpb.FieldDescriptorProto_TYPE_UINT32,
+			descriptorpb.FieldDescriptorProto_TYPE_FIXED32:
+			converter = func(v string) (interface{}, error) {
+				u, err := strconv.ParseUint(v, 10, 32)
+				return uint32(u), err
+			}
 
-	options := make([]string, len(oneof.GetChoices()))
-	fieldOf := map[string]*desc.FieldDescriptor{}
-	for i, choice := range oneof.GetChoices() {
-		options[i] = choice.GetName()
-		fieldOf[choice.GetName()] = choice
-	}
+		case descriptorpb.FieldDescriptorProto_TYPE_BOOL:
+			converter = func(v string) (interface{}, error) { return strconv.ParseBool(v) }
+
+		case descriptorpb.FieldDescriptorProto_TYPE_STRING:
+			converter = func(v string) (interface{}, error) { return v, nil }
+
+		// Use strconv.Unquote to interpret byte literals and Unicode literals.
+		// For example, a user inputs `\x6f\x67\x69\x73\x6f`,
+		// His expects "ogiso" in string, but backslashes in the input are not interpreted as an escape sequence.
+		// So, we need to call strconv.Unquote to interpret backslashes as an escape sequence.
+		case descriptorpb.FieldDescriptorProto_TYPE_BYTES:
+			converter = func(v string) (interface{}, error) {
+				if r.opts.BytesFromFile {
+					b, err := ioutil.ReadFile(v)
+					if err == nil {
+						return b, nil
+					}
+				}
 
-	choice, err := f.prompt.Select(oneof.GetName(), options)
-	if err != nil {
-		return nil, err
-	}
+				v, err := strconv.Unquote(`"` + v + `"`)
+				return []byte(v), err
+			}
 
-	desc, ok := fieldOf[choice]
-	if !ok {
-		return nil, errors.Errorf("invalid choice '%s' selected", choice)
-	}
+		default:
+			return nil, fmt.Errorf("invalid type: %s", t)
+		}
 
-	return desc, nil
-}
+		prefix := r.makePrefix(f)
 
-func (f *InteractiveFiller) selectEnum(enum *desc.FieldDescriptor) (*desc.EnumValueDescriptor, error) {
-	values := enum.GetEnumType().GetValues()
-	options := make([]string, 0, len(values))
-	valOf := map[string]*desc.EnumValueDescriptor{}
-	for _, v := range values {
-		options = append(options, v.GetName())
-		valOf[v.GetName()] = v
+		return r.input(prefix, f, converter)
 	}
 
-	choice, err := f.prompt.Select(enum.GetName(), options)
-	if err != nil {
-		return nil, err
-	}
+	if !f.IsRepeated() {
+		v, err := resolve(f)
+		if err != nil {
+			return err
+		}
 
-	c, ok := valOf[choice]
-	if !ok {
-		return nil, errors.Errorf("unknown enum '%s'", choice)
+		return r.msg.TrySetField(f, v)
 	}
 
-	return c, nil
-}
+	color := r.color
 
-func (f *InteractiveFiller) inputRepeatedField(dmsg *dynamic.Message, field *desc.FieldDescriptor) error {
-	old := f.prompt
-	defer func() {
-		f.prompt = old
-	}()
-
-	// If repeated fields, create new prompt. The prompt will be terminate with ctrl+d.
 	for {
-		f.prompt.SetPrefixColor(f.state.color)
+		// Return nil to keep inputted values.
+		if !r.addRepeatedField(f) {
+			return nil
+		}
+
+		r.prompt.SetPrefixColor(color)
+		color.Next()
 
-		err := f.inputField(dmsg, field, true)
-		if errors.Is(err, io.EOF) {
+		v, err := resolve(f)
+		if err == io.EOF {
+			// io.EOF signals the end of inputting repeated field.
+			// Return nil to keep inputted values.
 			return nil
 		}
 		if err != nil {
 			return err
 		}
 
-		f.state.color.Next()
+		if err := r.msg.TryAddRepeatedField(f, v); err != nil {
+			return err
+		}
 	}
 }
 
-// inputPrimitiveField reads an input and converts it to a Go type.
-// If CTRL+d is entered, inputPrimitiveField returns io.EOF.
-func (f *InteractiveFiller) inputPrimitiveField(fieldType descriptor.FieldDescriptorProto_Type) (interface{}, error) {
-	in, err := f.prompt.Input()
-	if errors.Is(err, io.EOF) {
-		return "", io.EOF
+func (r *resolver) resolveEnum(e *desc.EnumDescriptor) (int32, error) {
+	choices := make([]string, 0, len(e.GetValues()))
+	for _, v := range e.GetValues() {
+		choices = append(choices, v.GetName())
 	}
+
+	choice, err := r.selectChoices(e.GetFullyQualifiedName(), choices)
 	if err != nil {
-		return "", errors.Wrap(err, "failed to read user input")
+		return 0, err
 	}
 
-	v, err := convertValue(in, fieldType)
+	return int32(choice), nil
+}
+
+func (r *resolver) input(prefix string, f *desc.FieldDescriptor, converter func(string) (interface{}, error)) (interface{}, error) {
+	r.prompt.SetPrefix(prefix)
+	r.prompt.SetPrefixColor(r.color)
+
+	in, err := r.prompt.Input()
 	if err != nil {
 		return nil, err
 	}
+	if in == "" {
+		if f.IsRepeated() {
+			builder, err := builder.FromField(f)
+			if err != nil {
+				return nil, err
+			}
 
-	if fieldType == descriptor.FieldDescriptorProto_TYPE_BYTES {
-		return f.processBytesInput(v)
+			// Clear "repeated".
+			builder.Label = descriptorpb.FieldDescriptorProto_Label(0)
+			f, err = builder.Build()
+			if err != nil {
+				return nil, err
+			}
+		}
+		return f.GetDefaultValue(), nil
 	}
 
-	return v, err
+	return converter(in)
 }
 
-func (f *InteractiveFiller) processBytesInput(v interface{}) (interface{}, error) {
-	if _, ok := v.([]byte); !ok {
-		return nil, errors.New("value is not of type bytes")
+func (r *resolver) selectChoices(msg string, choices []string) (int, error) {
+	n, _, err := r.prompt.Select(msg, choices)
+	if errors.Is(err, prompt.ErrAbort) {
+		// Skip inputting and use default value.
+		return 0, nil
 	}
-
-	if f.bytesFromFile {
-		return readFileFromRelativePath(string(v.([]byte)))
+	if errors.Is(err, io.EOF) {
+		return 0, io.EOF
+	}
+	if err != nil {
+		return 0, err
 	}
 
-	return v, nil
-}
-
-func (f *InteractiveFiller) isSelectedOneOf(field *desc.FieldDescriptor) bool {
-	_, ok := f.state.selectedOneOf[field.GetOneOf().GetFullyQualifiedName()]
-	return ok
+	return n, nil
 }
 
-// isCirculatedField checks whether the passed message m is a circulated message. Field f must be a message field.
-func (f *InteractiveFiller) isCirculatedField(field *desc.FieldDescriptor) bool {
-	var appearedFields []*desc.FieldDescriptor
-	appeared := make(map[string]interface{})
-
-	var checkCirculatedField func(*desc.FieldDescriptor)
-	checkCirculatedField = func(field *desc.FieldDescriptor) {
-		appeared[field.GetMessageType().GetFullyQualifiedName()] = nil
-		appearedFields = append(appearedFields, field)
-		copiedAppeared := make(map[string]interface{})
-		for k, v := range appeared {
-			copiedAppeared[k] = v
+func (r *resolver) addRepeatedField(f *desc.FieldDescriptor) bool {
+	if !r.opts.AddRepeatedManually {
+		if f.GetType() != descriptorpb.FieldDescriptorProto_TYPE_MESSAGE || len(f.GetMessageType().GetFields()) != 0 {
+			return true
 		}
-		copiedAppearedFields := make([]*desc.FieldDescriptor, len(appearedFields))
-		copy(copiedAppearedFields, appearedFields)
-
-		defer func() {
-			appeared = copiedAppeared
-			appearedFields = copiedAppearedFields
-		}()
 
-		for _, field := range field.GetMessageType().GetFields() {
-			msg := field.GetMessageType()
-			if msg == nil {
-				continue
-			}
-			msgName := msg.GetFullyQualifiedName()
-			// If msgName is already appeared, the message is a circulated message.
-			// Find the duplicate field from the top of appearedFields,
-			// and let it be the part that circulates.
-			if _, found := appeared[msgName]; found {
-				for idx := 0; idx < len(appearedFields); idx++ {
-					if appearedFields[idx].GetMessageType().GetFullyQualifiedName() != msgName {
-						continue
-					}
-					appearedMsgs := make([]string, len(appearedFields[idx:]))
-					for i, f := range appearedFields[idx:] {
-						appearedMsgs[i] = f.GetMessageType().GetFullyQualifiedName()
-					}
-					f.state.circulatedMessages[appearedFields[idx].GetFullyQualifiedName()] = appearedMsgs
-				}
-				return
-			}
-			checkCirculatedField(field)
-		}
+		// f is repeated empty message field. It will cause infinite-loop if r.opts.AddRepeatedManually is false.
+		// For user's experience, always display prompt in this case.
 	}
 
-	checkCirculatedField(field)
-	return len(f.state.circulatedMessages[field.GetFullyQualifiedName()]) != 0
-}
+	msg := fmt.Sprintf("add a repeated field value? field=%s", f.GetFullyQualifiedName())
+	choices := []string{"yes", "no"}
+	n, _, err := r.prompt.Select(msg, choices)
+	if err != nil || n == 1 {
+		return false
+	}
 
-// makePrefix makes prefix for field f.
-func (f *InteractiveFiller) makePrefix(field *desc.FieldDescriptor) string {
-	return makePrefix(f.prefixFormat, field, f.state.ancestor, f.state.hasAncestorAndHasRepeatedField)
+	return true
 }
 
-var initialPromptInputterState = promptInputterState{
-	selectedOneOf:      make(map[string]interface{}),
-	circulatedMessages: make(map[string][]string),
-	color:              prompt.ColorInitial,
-}
+func (r *resolver) skipMessage(f *desc.FieldDescriptor) bool {
+	if !r.opts.DigManually {
+		return false
+	}
 
-// promptInputterState holds states for inputting a message.
-type promptInputterState struct {
-	// Key: fully-qualified message name, Val: nil
-	selectedOneOf map[string]interface{}
-	// circulatedMessages holds a fully-qualified message name,
-	// and the val represents whether the message circulates or not.
-	// If a message circulates, val holds a slice of names from the key message until the last message.
-	// If a message doesn't circulate, the val is nil.
-	//
-	// A key is assigned at calling a RPC that requires the message.
-	circulatedMessages map[string][]string
-
-	ancestor []string
-	color    prompt.Color
-	// The field has parent fields and one or more fields is/are repeated.
-	hasAncestorAndHasRepeatedField bool
+	msg := fmt.Sprintf("dig down? field=%s", f.GetFullyQualifiedName())
+	n, _, _ := r.prompt.Select(msg, []string{"dig down", "skip"})
+	return n == 1
 }
 
-// clone copies itself. Copied map fields refer to another position from the original.
-func (s promptInputterState) clone() promptInputterState {
-	new := s
-	newSelectedOneOf := make(map[string]interface{})
-	for k, v := range s.selectedOneOf {
-		newSelectedOneOf[k] = v
-	}
-	new.selectedOneOf = newSelectedOneOf
-	newCirculatedMessages := make(map[string][]string)
-	for k, v := range s.circulatedMessages {
-		newCirculatedMessages[k] = v
-	}
-	new.circulatedMessages = newCirculatedMessages
-	return new
-}
+func (r *resolver) makePrefix(field *desc.FieldDescriptor) string {
+	const delimiter = "::"
 
-const (
-	repeatedStr       = "<repeated> "
-	ancestorDelimiter = "::"
-)
-
-func makePrefix(s string, field *desc.FieldDescriptor, ancestor []string, ancestorHasRepeated bool) string {
-	joinedAncestor := strings.Join(ancestor, ancestorDelimiter)
+	joinedAncestor := strings.Join(r.ancestors, delimiter)
 	if joinedAncestor != "" {
-		joinedAncestor += ancestorDelimiter
+		joinedAncestor += delimiter
 	}
 
+	s := r.prefixFormat
+
 	s = strings.ReplaceAll(s, "{ancestor}", joinedAncestor)
 	s = strings.ReplaceAll(s, "{name}", field.GetName())
 	s = strings.ReplaceAll(s, "{type}", field.GetType().String())
 
-	if field.IsRepeated() || ancestorHasRepeated {
-		return repeatedStr + s
+	if r.repeated || field.IsRepeated() {
+		return "<repeated> " + s
 	}
-	return s
-}
 
-func isOneOfField(field *desc.FieldDescriptor) bool {
-	return field.GetOneOf() != nil
-}
-
-func readFileFromRelativePath(path string) ([]byte, error) {
-	if path == "" {
-		return nil, nil
-	}
-	return ioutil.ReadFile(path)
+	return s
 }
diff --git a/prompt/prompt.go b/prompt/prompt.go
index 9be29c1..72e517a 100644
--- a/prompt/prompt.go
+++ b/prompt/prompt.go
@@ -48,12 +48,17 @@ func (c *Color) Next() {
 	*c = (*c + 1) % 16
 }
 
+// NextVal is the same as Next, but return color as value.
+func (c *Color) NextVal() Color {
+	return (*c + 1) % 16
+}
+
 type Prompt interface {
 	// Input reads keyboard input.
 	// If ctrl+d is entered, Input returns io.EOF.
 	// If ctrl+c is entered, Input returns ErrAbort.
 	Input() (string, error)
-	Select(message string, options []string) (selected string, _ error)
+	Select(message string, options []string) (idx int, selected string, _ error)
 
 	// SetPrefix changes the current prefix to the passed one.
 	SetPrefix(prefix string)
@@ -81,10 +86,9 @@ func newPrompt(opts ...Option) Prompt {
 	p := &prompt{
 		InputFunc:   goprompt.Input,
 		prefixColor: ColorInitial,
-		SelectFunc: func(message string, options []string) (string, error) {
+		SelectFunc: func(message string, options []string) (int, string, error) {
 			s := promptui.Select{Label: message, Items: options}
-			_, res, err := s.Run()
-			return res, err
+			return s.Run()
 		},
 		commandHistory: opt.commandHistory,
 	}
@@ -116,7 +120,7 @@ type prompt struct {
 
 	// Treat prompt functions as fields for testing.
 	InputFunc  func(prefix string, completer goprompt.Completer, opts ...goprompt.Option) (string, error)
-	SelectFunc func(message string, options []string) (string, error)
+	SelectFunc func(message string, options []string) (int, string, error)
 }
 
 func (p *prompt) Input() (in string, err error) {
@@ -137,20 +141,18 @@ func (p *prompt) Input() (in string, err error) {
 	return in, nil
 }
 
-func (p *prompt) Select(message string, options []string) (string, error) {
-	for {
-		res, err := p.SelectFunc(message, options)
-		if errors.Is(err, promptui.ErrInterrupt) {
-			continue
-		}
-		if errors.Is(err, promptui.ErrEOF) {
-			return "", io.EOF
-		}
-		if err != nil {
-			return "", errors.Wrap(err, "failed to select an item")
-		}
-		return res, nil
+func (p *prompt) Select(message string, options []string) (int, string, error) {
+	n, res, err := p.SelectFunc(message, options)
+	if errors.Is(err, promptui.ErrInterrupt) {
+		return 0, "", ErrAbort
+	}
+	if errors.Is(err, promptui.ErrEOF) {
+		return 0, "", io.EOF
+	}
+	if err != nil {
+		return 0, "", errors.Wrap(err, "failed to select an item")
 	}
+	return n, res, nil
 }
 
 func (p *prompt) SetPrefix(prefix string) {
diff --git a/repl/commands.go b/repl/commands.go
index fa403f3..6ba677e 100644
--- a/repl/commands.go
+++ b/repl/commands.go
@@ -155,7 +155,7 @@ func (c *showCommand) Run(w io.Writer, args []string) error {
 }
 
 type callCommand struct {
-	enrich, digManually, bytesFromFile, emitDefaults, repeatCall bool
+	enrich, digManually, bytesFromFile, emitDefaults, repeatCall, addRepeatedManually bool
 }
 
 func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) {
@@ -166,6 +166,7 @@ func (c *callCommand) FlagSet() (*pflag.FlagSet, bool) {
 	fs.BoolVar(&c.bytesFromFile, "bytes-from-file", false, "interpret TYPE_BYTES input as a relative path to a file")
 	fs.BoolVar(&c.emitDefaults, "emit-defaults", false, "render fields with default values")
 	fs.BoolVarP(&c.repeatCall, "repeat", "r", false, "repeat previous unary or server streaming request (if exists)")
+	fs.BoolVar(&c.addRepeatedManually, "add-repeated-manually", false, "prompt asks whether to add a value if it encountered to a repeated field")
 	return fs, true
 }
 
@@ -200,7 +201,7 @@ func (c *callCommand) Run(w io.Writer, args []string) error {
 
 	// here we create the request context
 	// we also add the call command flags here
-	err := usecase.CallRPCInteractively(context.Background(), w, args[0], c.digManually, c.bytesFromFile, c.repeatCall)
+	err := usecase.CallRPCInteractively(context.Background(), w, args[0], c.digManually, c.bytesFromFile, c.repeatCall, c.addRepeatedManually)
 	if errors.Is(err, io.EOF) {
 		return errors.New("inputting canceled")
 	}
diff --git a/usecase/call_rpc.go b/usecase/call_rpc.go
index 4103aff..e5f9415 100644
--- a/usecase/call_rpc.go
+++ b/usecase/call_rpc.go
@@ -478,14 +478,18 @@ func (f *interactiveFiller) Fill(v interface{}) error {
 	return f.fillFunc(v)
 }
 
-func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile bool, rerunPrevious bool) error {
-	return dm.CallRPCInteractively(ctx, w, rpcName, digManually, bytesFromFile, rerunPrevious)
+func CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile, rerunPrevious, addRepeatedManually bool) error {
+	return dm.CallRPCInteractively(ctx, w, rpcName, digManually, bytesFromFile, rerunPrevious, addRepeatedManually)
 }
 
-func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile, rerunPrevious bool) error {
+func (m *dependencyManager) CallRPCInteractively(ctx context.Context, w io.Writer, rpcName string, digManually, bytesFromFile, rerunPrevious, addRepeatedManually bool) error {
 	return m.CallRPC(ctx, w, rpcName, rerunPrevious, &interactiveFiller{
 		fillFunc: func(v interface{}) error {
-			return m.interactiveFiller.Fill(v, fill.InteractiveFillerOpts{DigManually: digManually, BytesFromFile: bytesFromFile})
+			return m.interactiveFiller.Fill(v, fill.InteractiveFillerOpts{
+				DigManually:         digManually,
+				BytesFromFile:       bytesFromFile,
+				AddRepeatedManually: addRepeatedManually,
+			})
 		},
 	})
 }


Test patch:
diff --git a/app/update_test.go b/app/update_test.go
index 1bead78..49ef400 100644
--- a/app/update_test.go
+++ b/app/update_test.go
@@ -367,6 +367,6 @@ type promptMock struct {
 	err    error
 }
 
-func (p *promptMock) Select(_ string, options []string) (string, error) {
-	return p.result, p.err
+func (p *promptMock) Select(_ string, options []string) (int, string, error) {
+	return 0, p.result, p.err
 }
diff --git a/e2e/e2egen/e2egen.go b/e2e/e2egen/e2egen.go
index fd80ed2..4b4b332 100644
--- a/e2e/e2egen/e2egen.go
+++ b/e2e/e2egen/e2egen.go
@@ -209,12 +209,12 @@ func (p *recorderPrompt) Input() (string, error) {
 	return s, err
 }
 
-func (p *recorderPrompt) Select(message string, options []string) (string, error) {
-	s, err := p.Prompt.Select(message, options)
+func (p *recorderPrompt) Select(message string, options []string) (int, string, error) {
+	_, s, err := p.Prompt.Select(message, options)
 	if err == nil {
 		p.inputHistory = append(p.inputHistory, s)
 	}
-	return s, err
+	return 0, s, err
 }
 
 func filterArgs(args []string) []string {
diff --git a/e2e/e2egen/mock_gen.go b/e2e/e2egen/mock_gen.go
index 97eecaa..84eca10 100644
--- a/e2e/e2egen/mock_gen.go
+++ b/e2e/e2egen/mock_gen.go
@@ -8,49 +8,40 @@ import (
 	"sync"
 )
 
-var (
-	lockPromptMockGetCommandHistory sync.RWMutex
-	lockPromptMockInput             sync.RWMutex
-	lockPromptMockSelect            sync.RWMutex
-	lockPromptMockSetCompleter      sync.RWMutex
-	lockPromptMockSetPrefix         sync.RWMutex
-	lockPromptMockSetPrefixColor    sync.RWMutex
-)
-
-// Ensure, that PromptMock does implement Prompt.
+// Ensure, that PromptMock does implement prompt.Prompt.
 // If this is not the case, regenerate this file with moq.
 var _ prompt.Prompt = &PromptMock{}
 
-// PromptMock is a mock implementation of Prompt.
+// PromptMock is a mock implementation of prompt.Prompt.
 //
-//     func TestSomethingThatUsesPrompt(t *testing.T) {
+// 	func TestSomethingThatUsesPrompt(t *testing.T) {
 //
-//         // make and configure a mocked Prompt
-//         mockedPrompt := &PromptMock{
-//             GetCommandHistoryFunc: func() []string {
-// 	               panic("mock out the GetCommandHistory method")
-//             },
-//             InputFunc: func() (string, error) {
-// 	               panic("mock out the Input method")
-//             },
-//             SelectFunc: func(message string, options []string) (string, error) {
-// 	               panic("mock out the Select method")
-//             },
-//             SetCompleterFunc: func(c prompt.Completer)  {
-// 	               panic("mock out the SetCompleter method")
-//             },
-//             SetPrefixFunc: func(prefix string)  {
-// 	               panic("mock out the SetPrefix method")
-//             },
-//             SetPrefixColorFunc: func(color prompt.Color)  {
-// 	               panic("mock out the SetPrefixColor method")
-//             },
-//         }
+// 		// make and configure a mocked prompt.Prompt
+// 		mockedPrompt := &PromptMock{
+// 			GetCommandHistoryFunc: func() []string {
+// 				panic("mock out the GetCommandHistory method")
+// 			},
+// 			InputFunc: func() (string, error) {
+// 				panic("mock out the Input method")
+// 			},
+// 			SelectFunc: func(message string, options []string) (int, string, error) {
+// 				panic("mock out the Select method")
+// 			},
+// 			SetCompleterFunc: func(c prompt.Completer)  {
+// 				panic("mock out the SetCompleter method")
+// 			},
+// 			SetPrefixFunc: func(prefix string)  {
+// 				panic("mock out the SetPrefix method")
+// 			},
+// 			SetPrefixColorFunc: func(color prompt.Color)  {
+// 				panic("mock out the SetPrefixColor method")
+// 			},
+// 		}
 //
-//         // use mockedPrompt in code that requires Prompt
-//         // and then make assertions.
+// 		// use mockedPrompt in code that requires prompt.Prompt
+// 		// and then make assertions.
 //
-//     }
+// 	}
 type PromptMock struct {
 	// GetCommandHistoryFunc mocks the GetCommandHistory method.
 	GetCommandHistoryFunc func() []string
@@ -59,7 +50,7 @@ type PromptMock struct {
 	InputFunc func() (string, error)
 
 	// SelectFunc mocks the Select method.
-	SelectFunc func(message string, options []string) (string, error)
+	SelectFunc func(message string, options []string) (int, string, error)
 
 	// SetCompleterFunc mocks the SetCompleter method.
 	SetCompleterFunc func(c prompt.Completer)
@@ -101,6 +92,12 @@ type PromptMock struct {
 			Color prompt.Color
 		}
 	}
+	lockGetCommandHistory sync.RWMutex
+	lockInput             sync.RWMutex
+	lockSelect            sync.RWMutex
+	lockSetCompleter      sync.RWMutex
+	lockSetPrefix         sync.RWMutex
+	lockSetPrefixColor    sync.RWMutex
 }
 
 // GetCommandHistory calls GetCommandHistoryFunc.
@@ -110,9 +107,9 @@ func (mock *PromptMock) GetCommandHistory() []string {
 	}
 	callInfo := struct {
 	}{}
-	lockPromptMockGetCommandHistory.Lock()
+	mock.lockGetCommandHistory.Lock()
 	mock.calls.GetCommandHistory = append(mock.calls.GetCommandHistory, callInfo)
-	lockPromptMockGetCommandHistory.Unlock()
+	mock.lockGetCommandHistory.Unlock()
 	return mock.GetCommandHistoryFunc()
 }
 
@@ -123,9 +120,9 @@ func (mock *PromptMock) GetCommandHistoryCalls() []struct {
 } {
 	var calls []struct {
 	}
-	lockPromptMockGetCommandHistory.RLock()
+	mock.lockGetCommandHistory.RLock()
 	calls = mock.calls.GetCommandHistory
-	lockPromptMockGetCommandHistory.RUnlock()
+	mock.lockGetCommandHistory.RUnlock()
 	return calls
 }
 
@@ -136,9 +133,9 @@ func (mock *PromptMock) Input() (string, error) {
 	}
 	callInfo := struct {
 	}{}
-	lockPromptMockInput.Lock()
+	mock.lockInput.Lock()
 	mock.calls.Input = append(mock.calls.Input, callInfo)
-	lockPromptMockInput.Unlock()
+	mock.lockInput.Unlock()
 	return mock.InputFunc()
 }
 
@@ -149,14 +146,14 @@ func (mock *PromptMock) InputCalls() []struct {
 } {
 	var calls []struct {
 	}
-	lockPromptMockInput.RLock()
+	mock.lockInput.RLock()
 	calls = mock.calls.Input
-	lockPromptMockInput.RUnlock()
+	mock.lockInput.RUnlock()
 	return calls
 }
 
 // Select calls SelectFunc.
-func (mock *PromptMock) Select(message string, options []string) (string, error) {
+func (mock *PromptMock) Select(message string, options []string) (int, string, error) {
 	if mock.SelectFunc == nil {
 		panic("PromptMock.SelectFunc: method is nil but Prompt.Select was just called")
 	}
@@ -167,9 +164,9 @@ func (mock *PromptMock) Select(message string, options []string) (string, error)
 		Message: message,
 		Options: options,
 	}
-	lockPromptMockSelect.Lock()
+	mock.lockSelect.Lock()
 	mock.calls.Select = append(mock.calls.Select, callInfo)
-	lockPromptMockSelect.Unlock()
+	mock.lockSelect.Unlock()
 	return mock.SelectFunc(message, options)
 }
 
@@ -184,9 +181,9 @@ func (mock *PromptMock) SelectCalls() []struct {
 		Message string
 		Options []string
 	}
-	lockPromptMockSelect.RLock()
+	mock.lockSelect.RLock()
 	calls = mock.calls.Select
-	lockPromptMockSelect.RUnlock()
+	mock.lockSelect.RUnlock()
 	return calls
 }
 
@@ -200,9 +197,9 @@ func (mock *PromptMock) SetCompleter(c prompt.Completer) {
 	}{
 		C: c,
 	}
-	lockPromptMockSetCompleter.Lock()
+	mock.lockSetCompleter.Lock()
 	mock.calls.SetCompleter = append(mock.calls.SetCompleter, callInfo)
-	lockPromptMockSetCompleter.Unlock()
+	mock.lockSetCompleter.Unlock()
 	mock.SetCompleterFunc(c)
 }
 
@@ -215,9 +212,9 @@ func (mock *PromptMock) SetCompleterCalls() []struct {
 	var calls []struct {
 		C prompt.Completer
 	}
-	lockPromptMockSetCompleter.RLock()
+	mock.lockSetCompleter.RLock()
 	calls = mock.calls.SetCompleter
-	lockPromptMockSetCompleter.RUnlock()
+	mock.lockSetCompleter.RUnlock()
 	return calls
 }
 
@@ -231,9 +228,9 @@ func (mock *PromptMock) SetPrefix(prefix string) {
 	}{
 		Prefix: prefix,
 	}
-	lockPromptMockSetPrefix.Lock()
+	mock.lockSetPrefix.Lock()
 	mock.calls.SetPrefix = append(mock.calls.SetPrefix, callInfo)
-	lockPromptMockSetPrefix.Unlock()
+	mock.lockSetPrefix.Unlock()
 	mock.SetPrefixFunc(prefix)
 }
 
@@ -246,9 +243,9 @@ func (mock *PromptMock) SetPrefixCalls() []struct {
 	var calls []struct {
 		Prefix string
 	}
-	lockPromptMockSetPrefix.RLock()
+	mock.lockSetPrefix.RLock()
 	calls = mock.calls.SetPrefix
-	lockPromptMockSetPrefix.RUnlock()
+	mock.lockSetPrefix.RUnlock()
 	return calls
 }
 
@@ -262,9 +259,9 @@ func (mock *PromptMock) SetPrefixColor(color prompt.Color) {
 	}{
 		Color: color,
 	}
-	lockPromptMockSetPrefixColor.Lock()
+	mock.lockSetPrefixColor.Lock()
 	mock.calls.SetPrefixColor = append(mock.calls.SetPrefixColor, callInfo)
-	lockPromptMockSetPrefixColor.Unlock()
+	mock.lockSetPrefixColor.Unlock()
 	mock.SetPrefixColorFunc(color)
 }
 
@@ -277,8 +274,8 @@ func (mock *PromptMock) SetPrefixColorCalls() []struct {
 	var calls []struct {
 		Color prompt.Color
 	}
-	lockPromptMockSetPrefixColor.RLock()
+	mock.lockSetPrefixColor.RLock()
 	calls = mock.calls.SetPrefixColor
-	lockPromptMockSetPrefixColor.RUnlock()
+	mock.lockSetPrefixColor.RUnlock()
 	return calls
 }
diff --git a/e2e/old_repl_test.go b/e2e/old_repl_test.go
index e44ba6a..5d4066d 100644
--- a/e2e/old_repl_test.go
+++ b/e2e/old_repl_test.go
@@ -99,9 +99,13 @@ func TestE2E_OldREPL(t *testing.T) {
 			args:  "testdata/test.proto",
 			input: []interface{}{"call UnaryRepeated", "miyuki", "kaguya", "chika", "yu", io.EOF},
 		},
+		"call UnaryRepeated (with --add-repeated-manually)": {
+			args:  "testdata/test.proto",
+			input: []interface{}{"call --add-repeated-manually UnaryRepeated", 0, "miyuki", 0, "kaguya", 0, "chika", 0, "yu", 1},
+		},
 		"call UnarySelf": {
 			args:  "testdata/test.proto",
-			input: []interface{}{"call UnarySelf", "dig down", "ohana", "matsumae", "ohana", "dig down", "nako", "oshimizu", "nakochi", "finish", "dig down", "minko", "tsurugi", "minchi", "finish", "finish"},
+			input: []interface{}{"call UnarySelf", "ohana", "matsumae", "ohana", "nako", "oshimizu", "nakochi", io.EOF, "minko", "tsurugi", "minchi", io.EOF, io.EOF},
 		},
 		"call UnaryMap": {
 			args:       "testdata/test.proto",
@@ -110,11 +114,11 @@ func TestE2E_OldREPL(t *testing.T) {
 		},
 		"call UnaryOneof": {
 			args:  "testdata/test.proto",
-			input: []interface{}{"call UnaryOneof", "msg", "ai", "hayasaka"},
+			input: []interface{}{"call UnaryOneof", 0, "ai", "hayasaka"},
 		},
 		"call UnaryEnum": {
 			args:  "testdata/test.proto",
-			input: []interface{}{"call UnaryEnum", "Male"},
+			input: []interface{}{"call UnaryEnum", 0},
 		},
 		"call UnaryBytes": {
 			args:  "testdata/test.proto",
@@ -122,7 +126,7 @@ func TestE2E_OldREPL(t *testing.T) {
 		},
 		"call UnaryRepeatedEnum": {
 			args:  "testdata/test.proto",
-			input: []interface{}{"call UnaryRepeatedEnum", "Male", "Male", "Female", io.EOF},
+			input: []interface{}{"call UnaryRepeatedEnum", 0, 0, 1, io.EOF},
 		},
 
 		// call (gRPC-Web)
@@ -272,7 +276,7 @@ func TestE2E_OldREPL(t *testing.T) {
 		},
 		"ctrl-c skips the rest of the current message and exits the repeated field": {
 			args:  "testdata/test.proto",
-			input: []interface{}{"call UnaryRepeatedMessage", "kanade", "hisaishi", "kumiko", prompt.ErrAbort},
+			input: []interface{}{"call UnaryRepeatedMessage", "kanade", "hisaishi", "kumiko", prompt.ErrAbort, io.EOF},
 		},
 		"ctrl-c is also enabled in streaming RPCs": {
 			args:  "testdata/test.proto",
@@ -342,9 +346,12 @@ type stubPrompt struct {
 }
 
 func (p *stubPrompt) Input() (string, error) {
+	p.t.Helper()
+
 	if len(p.input) == 0 {
 		p.t.Fatal("p.input is empty, but testing is continued yet. Are you forgot to use io.EOF for finishing inputting?")
 	}
+
 	s := p.input[0]
 	p.input = p.input[1:]
 	switch e := s.(type) {
@@ -353,12 +360,31 @@ func (p *stubPrompt) Input() (string, error) {
 	case error:
 		return "", e
 	default:
-		panic(fmt.Sprintf("expected string or error, but got '%T'", e))
+		p.t.Fatalf("expected string or error, but got '%T'", e)
+		return "", nil
 	}
 }
 
-func (p *stubPrompt) Select(string, []string) (string, error) {
-	return p.Input()
+func (p *stubPrompt) Select(string, []string) (int, string, error) {
+	p.t.Helper()
+
+	if len(p.input) == 0 {
+		p.t.Fatal("p.input is empty, but testing is continued yet. Are you forgot to use io.EOF for finishing inputting?")
+	}
+
+	s := p.input[0]
+	p.input = p.input[1:]
+
+	switch v := s.(type) {
+	case int:
+		// TODO: Remove 2nd arg.
+		return v, "", nil
+	case error:
+		return 0, "", v
+	default:
+		p.t.Fatalf("expected int or error, but got '%T'", v)
+		return 0, "", nil
+	}
 }
 
 var (
diff --git a/e2e/repl_test.go b/e2e/repl_test.go
index 5b1c174..79e6e13 100644
--- a/e2e/repl_test.go
+++ b/e2e/repl_test.go
@@ -106,9 +106,13 @@ func TestE2E_REPL(t *testing.T) {
 			commonFlags: "--proto testdata/test.proto",
 			input:       []interface{}{"call UnaryRepeated", "miyuki", "kaguya", "chika", "yu", io.EOF},
 		},
+		"call UnaryRepeated (specify default value)": {
+			commonFlags: "--proto testdata/test.proto",
+			input:       []interface{}{"call UnaryRepeated", "", io.EOF},
+		},
 		"call UnarySelf": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call UnarySelf", "dig down", "ohana", "matsumae", "ohana", "dig down", "nako", "oshimizu", "nakochi", "finish", "dig down", "minko", "tsurugi", "minchi", "finish", "finish"},
+			input:       []interface{}{"call UnarySelf", "ohana", "matsumae", "ohana", "nako", "oshimizu", "nakochi", io.EOF, "minko", "tsurugi", "minchi", io.EOF, io.EOF},
 		},
 		"call UnaryMap": {
 			commonFlags: "--proto testdata/test.proto",
@@ -117,11 +121,11 @@ func TestE2E_REPL(t *testing.T) {
 		},
 		"call UnaryOneof": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call UnaryOneof", "msg", "ai", "hayasaka"},
+			input:       []interface{}{"call UnaryOneof", 0, "ai", "hayasaka"},
 		},
 		"call UnaryEnum": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call UnaryEnum", "Male"},
+			input:       []interface{}{"call UnaryEnum", 0},
 		},
 		"call UnaryBytes": {
 			commonFlags: "--proto testdata/test.proto",
@@ -129,7 +133,7 @@ func TestE2E_REPL(t *testing.T) {
 		},
 		"call UnaryRepeatedEnum": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call UnaryRepeatedEnum", "Male", "Male", "Female", io.EOF},
+			input:       []interface{}{"call UnaryRepeatedEnum", 0, 0, 1, io.EOF},
 		},
 		"call Unary with an invalid flag": {
 			commonFlags: "--proto testdata/test.proto",
@@ -139,15 +143,15 @@ func TestE2E_REPL(t *testing.T) {
 		},
 		"call UnaryEcho": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call --dig-manually UnaryEcho", "dig down", "kaguya", "shinomiya"},
+			input:       []interface{}{"call --dig-manually UnaryEcho", 0, "kaguya", "shinomiya"},
 		},
 		"call UnaryEcho with empty request": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call --dig-manually UnaryEcho", "skip"},
+			input:       []interface{}{"call --dig-manually UnaryEcho", 1},
 		},
 		"call UnaryEcho with empty name request": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call --dig-manually UnaryEcho", "dig down", prompt.ErrAbort},
+			input:       []interface{}{"call --dig-manually UnaryEcho", 0, prompt.ErrAbort},
 		},
 
 		// call (gRPC-Web)
@@ -335,7 +339,7 @@ func TestE2E_REPL(t *testing.T) {
 		},
 		"ctrl-c skips the rest of the current message and exits the repeated field": {
 			commonFlags: "--proto testdata/test.proto",
-			input:       []interface{}{"call UnaryRepeatedMessage", "kanade", "hisaishi", "kumiko", prompt.ErrAbort},
+			input:       []interface{}{"call UnaryRepeatedMessage", "kanade", "hisaishi", "kumiko", prompt.ErrAbort, io.EOF},
 		},
 		"ctrl-c is also enabled in streaming RPCs": {
 			commonFlags: "--proto testdata/test.proto",
diff --git a/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated_(with_--add-repeated-manually).golden b/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated_(with_--add-repeated-manually).golden
new file mode 100644
index 0000000..86c7e65
--- /dev/null
+++ b/e2e/testdata/fixtures/teste2e_oldrepl-call_unaryrepeated_(with_--add-repeated-manually).golden
@@ -0,0 +1,4 @@
+{
+  "message": "miyuki, kaguya, chika, yu"
+}
+
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
index a8089f4..3fb24b3 100644
--- a/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
+++ b/e2e/testdata/fixtures/teste2e_repl-call_--help.golden
@@ -1,9 +1,10 @@
 usage: call <method name>
 
 Options:
-      --bytes-from-file   interpret TYPE_BYTES input as a relative path to a file
-      --dig-manually      prompt asks whether to dig down if it encountered to a message field
-      --emit-defaults     render fields with default values
-      --enrich            enrich response output includes header, message, trailer and status
-  -r, --repeat            repeat previous unary or server streaming request (if exists)
+      --add-repeated-manually   prompt asks whether to add a value if it encountered to a repeated field
+      --bytes-from-file         interpret TYPE_BYTES input as a relative path to a file
+      --dig-manually            prompt asks whether to dig down if it encountered to a message field
+      --emit-defaults           render fields with default values
+      --enrich                  enrich response output includes header, message, trailer and status
+  -r, --repeat                  repeat previous unary or server streaming request (if exists)
 
diff --git a/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated_(specify_default_value).golden b/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated_(specify_default_value).golden
new file mode 100644
index 0000000..311847d
--- /dev/null
+++ b/e2e/testdata/fixtures/teste2e_repl-call_unaryrepeated_(specify_default_value).golden
@@ -0,0 +1,2 @@
+{}
+
diff --git a/fill/proto/interactive_filler_test.go b/fill/proto/interactive_filler_test.go
index b466c87..98a5fdb 100644
--- a/fill/proto/interactive_filler_test.go
+++ b/fill/proto/interactive_filler_test.go
@@ -1,72 +1,132 @@
 package proto
 
 import (
+	"runtime"
 	"testing"
 
-	"github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/golang/protobuf/jsonpb" //nolint:staticcheck
+	"github.com/jhump/protoreflect/desc/builder"
+	"github.com/jhump/protoreflect/dynamic"
 	"github.com/ktr0731/evans/fill"
 	"github.com/ktr0731/evans/prompt"
 )
 
-type testPrompt struct {
+type stubPrompt struct {
 	prompt.Prompt
 
-	input string
-}
+	t *testing.T
+
+	input []string
 
-func (t *testPrompt) Input() (string, error) {
-	return t.input, nil
+	idx       int
+	selection []string
 }
 
-func TestInteractiveProtoFiller(t *testing.T) {
-	f := NewInteractiveFiller(nil, "")
-	err := f.Fill("invalid type", fill.InteractiveFillerOpts{})
-	if err != fill.ErrCodecMismatch {
-		t.Errorf("must return fill.ErrCodecMismatch because the arg is invalid type, but got: %s", err)
-	}
+func (p *stubPrompt) Input() (string, error) {
+	p.t.Helper()
 
-	tp := &testPrompt{
-		input: "../../go.mod",
+	if len(p.input) == 0 {
+		p.t.Fatal("no input")
 	}
 
-	f = NewInteractiveFiller(tp, "")
-	f.bytesFromFile = true
+	in := p.input[0]
+	p.input = p.input[1:]
 
-	var v interface{}
-	v, err = f.inputPrimitiveField(descriptor.FieldDescriptorProto_TYPE_BYTES)
-	if err != nil {
-		t.Error(err)
-	}
+	return in, nil
+}
+
+func (p *stubPrompt) Select(string, []string) (int, string, error) {
+	p.t.Helper()
 
-	if _, ok := v.([]byte); !ok {
-		t.Errorf("value should be of type []byte")
+	if len(p.selection) == 0 {
+		p.t.Fatal("no selection")
 	}
 
-	fileContent, err := readFileFromRelativePath(tp.input)
+	idx := p.idx
+	sel := p.selection[0]
+	p.idx++
+	p.selection = p.selection[1:]
+
+	return idx, sel, nil
+}
+
+func (p *stubPrompt) SetPrefix(string) {}
+
+func (p *stubPrompt) SetPrefixColor(prompt.Color) {}
+
+func TestInteractiveFiller(t *testing.T) {
+	b := builder.NewMessage("Message")
+	b.AddField(builder.NewField("a", builder.FieldTypeMessage(builder.NewMessage("SubMessage"))).SetRepeated())
+	b.AddField(builder.NewField("b", builder.FieldTypeEnum(
+		builder.NewEnum("Enum").
+			AddValue(builder.NewEnumValue("foo")).
+			AddValue(builder.NewEnumValue("bar")),
+	)))
+	b.AddField(builder.NewField("c", builder.FieldTypeDouble()))
+	b.AddField(builder.NewField("d", builder.FieldTypeFloat()))
+	b.AddField(builder.NewField("e", builder.FieldTypeInt64()))
+	b.AddField(builder.NewField("f", builder.FieldTypeSFixed64()))
+	b.AddField(builder.NewField("g", builder.FieldTypeSInt64()))
+	b.AddField(builder.NewField("h", builder.FieldTypeUInt64()))
+	b.AddField(builder.NewField("i", builder.FieldTypeFixed64()))
+	b.AddField(builder.NewField("j", builder.FieldTypeInt32()))
+	b.AddField(builder.NewField("k", builder.FieldTypeSFixed32()))
+	b.AddField(builder.NewField("l", builder.FieldTypeSInt32()))
+	b.AddField(builder.NewField("m", builder.FieldTypeUInt32()))
+	b.AddField(builder.NewField("n", builder.FieldTypeFixed32()))
+	b.AddField(builder.NewField("o", builder.FieldTypeBool()))
+	b.AddField(builder.NewField("p", builder.FieldTypeString()))
+	b.AddField(builder.NewField("q", builder.FieldTypeBytes()))
+	b.AddField(builder.NewField("r", builder.FieldTypeBytes()))
+	b.AddField(builder.NewField("s", builder.FieldTypeBytes()))
+	m, err := b.Build()
 	if err != nil {
-		t.Error(err)
+		t.Fatalf("Build should not return an error, but got '%s'", err)
 	}
 
-	if len(v.([]byte)) != len(fileContent) {
-		t.Error("contents should have the same length")
+	msg := dynamic.NewMessage(m)
+	p := &stubPrompt{
+		t: t,
+		input: []string{
+			"1.1",          // c
+			"1.2",          // d
+			"1",            // e
+			"2",            // f
+			"3",            // g
+			"4",            // h
+			"5",            // i
+			"6",            // j
+			"7",            // k
+			"8",            // l
+			"9",            // m
+			"10",           // n
+			"true",         // o
+			"foo",          // p
+			"bar",          // q
+			"\x62\x61\x7a", // r
+			"./proto.go",   // s
+		},
+		selection: []string{"0", "1", "1"},
+	}
+	f := NewInteractiveFiller(p, "")
+	if err := f.Fill(msg, fill.InteractiveFillerOpts{BytesFromFile: true}); err != nil {
+		t.Errorf("should not return an error, but got '%s'", err)
 	}
 
-	tp = &testPrompt{
-		input: "\\x6f\\x67\\x69\\x73\\x6f",
+	if runtime.GOOS == "windows" {
+		t.Skip()
+		return
 	}
 
-	f = NewInteractiveFiller(tp, "")
+	const want = `{"a":[{}],"b":2,"c":1.1,"d":1.2,"e":"1","f":"2","g":"3","h":"4","i":"5","j":6,"k":7,"l":8,"m":9,"n":10,"o":true,"p":"foo","q":"YmFy","r":"YmF6","s":"Ly8gUGFja2FnZSBwcm90byBwcm92aWRlcyBhIGZpbGxlciBpbXBsZW1lbnRhdGlvbiBmb3IgUHJvdG9jb2wgQnVmZmVycy4KcGFja2FnZSBwcm90bwo="}`
 
-	v, err = f.inputPrimitiveField(descriptor.FieldDescriptorProto_TYPE_BYTES)
+	marshaler := jsonpb.Marshaler{EmitDefaults: true}
+	got, err := marshaler.MarshalToString(msg)
 	if err != nil {
-		t.Error(err)
-	}
-
-	if _, ok := v.([]byte); !ok {
-		t.Errorf("value should be of type []byte")
+		t.Fatalf("MarshalToString should not return an error, but got '%s'", err)
 	}
 
-	if string(v.([]byte)) != "ogiso" {
-		t.Error("unequal content")
+	if want != got {
+		t.Errorf("want: %s\ngot: %s", want, got)
 	}
 }
diff --git a/prompt/prompt_test.go b/prompt/prompt_test.go
index 6adb88d..5ab7773 100644
--- a/prompt/prompt_test.go
+++ b/prompt/prompt_test.go
@@ -66,34 +66,30 @@ func TestPrompt_Select(t *testing.T) {
 	_ = counter
 
 	cases := map[string]struct {
-		SelectFunc func(message string, options []string) (string, error)
+		SelectFunc func(message string, options []string) (int, string, error)
 
 		expectedErr error
 	}{
 		"normal": {
-			SelectFunc: func(message string, options []string) (string, error) {
-				return "an selection", nil
+			SelectFunc: func(message string, options []string) (int, string, error) {
+				return 0, "an selection", nil
 			},
 		},
-		"retry if prompttui.ErrInterrupt is returned from SelectFunc": {
-			SelectFunc: func(message string, options []string) (string, error) {
-				// To escape from the loop, check whether this call is the second or not.
-				if counter != 1 {
-					counter++
-					return "", promptui.ErrInterrupt
-				}
-				return "an selection", nil
+		"returns ErrAbort if prompttui.ErrInterrupt is returned from SelectFunc": {
+			SelectFunc: func(message string, options []string) (int, string, error) {
+				return 0, "", promptui.ErrInterrupt
 			},
+			expectedErr: ErrAbort,
 		},
 		"returns io.EOF if promptui.ErrEOF is returned from SelectFunc": {
-			SelectFunc: func(message string, options []string) (string, error) {
-				return "", promptui.ErrEOF
+			SelectFunc: func(message string, options []string) (int, string, error) {
+				return 0, "", promptui.ErrEOF
 			},
 			expectedErr: io.EOF,
 		},
 		"returns an error if an error is returned from SelectFunc": {
-			SelectFunc: func(message string, options []string) (string, error) {
-				return "", io.ErrUnexpectedEOF
+			SelectFunc: func(message string, options []string) (int, string, error) {
+				return 0, "", io.ErrUnexpectedEOF
 			},
 			expectedErr: io.ErrUnexpectedEOF,
 		},
@@ -104,7 +100,7 @@ func TestPrompt_Select(t *testing.T) {
 		t.Run(name, func(t *testing.T) {
 			p := newPrompt()
 			p.(*prompt).SelectFunc = c.SelectFunc
-			_, err := p.Select("", []string{"foo", "bar"})
+			_, _, err := p.Select("", []string{"foo", "bar"})
 			if c.expectedErr == nil {
 				if err != nil {
 					t.Fatalf("Input must not return an error, but got nil")