prompt icon indicating copy to clipboard operation
prompt copied to clipboard

Scrollable multi-choose?

Open watzon opened this issue 1 year ago • 1 comments

I'm trying to use multichoose in a place where there could be a lot of options. Right now if there are too many options it seems to overflow out of the terminal and there's no way to see the options at the top of the list.

2 things that would be really helpful here are

  • Search
  • Have lists scroll by default, and have an option to set a limit for the number of options shown at a time

watzon avatar Oct 14 '23 05:10 watzon

I managed to almost solve the list scrolling issue with a custom theme, but it would be nice to have something baked in. Here's the code if anyone is curious:

package main

import (
	"fmt"
	"strings"

	"github.com/cqroot/prompt/constants"
	"github.com/cqroot/prompt/multichoose"
)

// Scrolling theme for cqroot/prompt. Allows items to be scrolled through
// using the arrow keys.
//
// choices is a list of strings to display.
// cursor is the index of the currently selected item.
// isSelected is a function that returns true if the item at the given index
func ThemeScroll(choices []string, cursor int, isSelected multichoose.IsSelected) string {
	s := strings.Builder{}
	s.WriteString("\n")

	lenChoices := len(choices)
	index := cursor
	limit := 10
	start := 0
	end := limit

	// Create a slice of items to display
	if lenChoices > limit {
		if index < limit/2 {
			end = limit
		} else if index >= limit/2 && index <= lenChoices-limit/2 {
			start = index - limit/2
			end = index + limit/2
			index = limit / 2
		} else {
			start = lenChoices - limit
			end = lenChoices
			index = limit - (lenChoices - index)
		}
	}

	// Loop through the slice and display each item.
	// We will determine if the item is selected or not by calling isSelected
	// with the index of the item, but since we're only displaying a slice of
	// the items we need to offset the index.
	for i, choice := range choices[start:end] {
		// offset represents the index of the item in the original list
		offset := i + start

		// Check if the item is the one at the cursor's location
		isCursor := index == i

		// Render item according to its state (selected + cursor / selected / normal)
		if isSelected(offset) {
			if isCursor {
				s.WriteString(constants.DefaultSelectedItemStyle.Render(fmt.Sprintf("[x] %s", choice)))
			} else {
				s.WriteString(constants.DefaultItemStyle.Render(fmt.Sprintf("[x] %s", choice)))
			}
		} else {
			if isCursor {
				s.WriteString(constants.DefaultSelectedItemStyle.Render(fmt.Sprintf("[•] %s", choice)))
			} else {
				s.WriteString(constants.DefaultItemStyle.Render(fmt.Sprintf("[ ] %s", choice)))
			}
		}

		s.WriteString("\n")
	}

	return s.String()
}

Just a minor bug to sort out

watzon avatar Oct 14 '23 06:10 watzon