AlecAivazis / survey

A golang library for building interactive and accessible prompts with full support for windows and posix terminals.
MIT License
4.07k stars 350 forks source link

Get Selected Index in AskOne #412

Closed jojomi closed 1 year ago

jojomi commented 2 years ago

I would like to extend survey for my purposes, actually making it more expressive using generics.

The idea is to be able to select a value from any type of slice without type assertions using a mapper func that returns a displayable string for every element of the slice:

func ChooseOneWithMapper[T any](question string, options []T, mapper func(t T) string) (T, error) {
    var tResult T

    // map options
    stringOpts := make([]string, len(options))
    for i, option := range options {
        stringOpts[i] = mapper(option)
    }

    prompt := &survey.Select{
        Message: question,
        Options: stringOpts,
    }

    var result string
    err := survey.AskOne(prompt, &result, nil)
    if err != nil {
        return tResult, err
    }

    // map back
    for i, option := range stringOpts {
        if option != result {
            continue
        }
        return options[i], nil
    }
    return tResult, fmt.Errorf("invalid selection: %s", result)
}

This is not perfect though because I have to map the selection back to the original slice which may or may not be correct. It would be reliably possible if I could get the index of the selection instead of the value. Is there a way to do so I overlooked in the current code?

mislav commented 2 years ago

If &result is a pointer to an int instead of a string, it will write the index of the selection instead of the text of the selection.

jojomi commented 2 years ago

Thank you, never thought about that!

jojomi commented 2 years ago

I tried to make use of that, but I think it is buggy:

package main

import (
    "fmt"

    "github.com/AlecAivazis/survey/v2"
)

func main() {
    var answer int

    prompt := &survey.Select{
        Message: "Choose a color:",
        Options: []string{"yellow", "red", "red", "blue"},
    }
    survey.AskOne(prompt, &answer)

    fmt.Printf("answer index returned: %d\n", answer)
}

When selecting the answers in separate runs, it returns 0, 2, 2, 3 respectively, but I would expect to see 0, 1, 2, 3. It seems buggy due to two answers sharing the same description string.

mislav commented 2 years ago

It seems buggy due to two answers sharing the same description string.

I think you are right. I will reopen to track returning the right index, even if options share the same string. Note that from the user's perspective, there should never be options with identical strings, otherwise the user can't know which one is "correct" to select.

jojomi commented 2 years ago

You are right about the UX concerns, but also about fixing it, since it totally seems possible to return the technically correct value here. Thank you already!