select5

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2025 License: Apache-2.0 Imports: 11 Imported by: 0

README

SELECT-5

codecov

Package select5 provides interactive terminal-based selection utilities for Go applications. It allows you to select items from lists and tables using keyboard navigation.

The package aims to provide simple and slim interactive layer for CLI written in Go.

For more rich experience (but fat package), use bubble or other solutions.

Overview

select5 offers two primary selection modes:

  • Simple string selection from a list
  • Advanced table row selection with mixed data types

Both modes support keyboard navigation with arrow keys and selection with Enter. The library handles terminal control sequences and cursor movement automatically.

The package also implements an experimental console text editor with Emacs-like key binding. For more detail, see the example.

Basic String Selection

Use SelectString to display a simple list of strings for selection:

selected, err := select5.SelectString([]string{"Option A", "Option B", "Option C"})
if err != nil {
	 log.Fatal(err)
}
fmt.Println("You selected:", selected)

Table Row Selection

For more complex data, SelectTableRow supports tables with mixed data types (integers, floats, strings, booleans):

data := [][]any{
	{"a", "Apple Inc.", 178.72, true},
	{"b", "Broadcom", 376.04, false},
	{"c", "Cisco", 125.30, true},
}

selectedRow, err := select5.SelectTableRow(data)
if err != nil {
	log.Fatal(err)
}

// Access selected data
code := selectedRow[0].(string)
name := selectedRow[1].(string)

fmt.Printf("Selected: %s - %s\n", code, name)

Generic entrypoint for data selector

For more flexible implementation, you can use Selector struct to declare selectable data which may be list or table of primitives, or any type.

strPointed := "WORD"
otherStrPointed := "VERB"
list := select5.Selector{
	Header: nil,
	Data: [][]any{
		{"AGI", 3, true, 3.58, nil},
		{"BGM", 2, true, 0.9, nil},
		{"CGC", 1, false, -93.20, &strPointed},
		{"DPO", 1829, true, 3.58, &otherStrPointed},
	},
}
res, _ := list.Select()
fmt.Printf("%v", res)

You can apply additional type casting for the interface (a.k.a. any type) results.

Type Helpers for primitives

The package includes helper functions to safely extract and convert values from the any type:

// Extract string value
code, err := select5.GetS(selectedRow[0])

// Extract float value
price, err := select5.GetF(selectedRow[2])

// Extract bool value
active, err := select5.GetB(selectedRow[3])

Builtin Type Detector for Selector

The Selector implements type detector in Type(), which returns type information in byte expression.

t := select5.Selector{
   Data:   []any{"a","b","c"},
}.Type()
// 0x01 == IsList | IsString

The type information is calculated by using following bitmask constants:

IsString byte = 0x01
IsInt    byte = 0x02
IsInt8   byte = 0x02
IsInt16  byte = 0x02

IsInt32   byte = 0x02
IsInt64   byte = 0x04
IsFloat32 byte = 0x08
IsFloat64 byte = 0x10
IsBool    byte = 0x20
IsPointer byte = 0x40
IsAny     byte = 0x7f
IsList    byte = 0x00
IsTable   byte = 0x80

For example, a float64 list will return 0x10 and a table of string and int values will return 0xff. (If one or more types have been detected in the data, the result will be a logical sum by IsAny 0x7f). You can implement type switcher for various data structures, using this mechanism.

Terminal Control

The package provides constants for terminal control operations:

// Position cursor
fmt.Printf(select5.MoveTo, line, column)

// Clear screen
fmt.Print(select5.ClearScreen)

// Reset cursor to home position
fmt.Print(select5.ResetCursor)

Keyboard Navigation for Selectors

  • Up/Down arrows: Move selection
  • Enter: Confirm selection
  • 'q' or Ctrl+C: Quit without selection

Error Handling

All selection functions return appropriate errors that should be checked:

  • Empty lists
  • Type conversion failures
  • Keyboard event channel closure
  • Interrupted selection

Text Editor (alpha)

select5 is not just a selection helper package, but also provides a tiny text editor. You can easily embed the editor in your application:

import (
	"fmt"
	"github.com/g1eng/select5"
	"os"
	"strings"
)

func main() {
	ed := select5.NewEditor()
	res := ed.Edit()
	t, err := os.CreateTemp(".", "result-*.txt")
	defer t.Close()
	if err != nil {
		panic(err)
	}
	fmt.Printf(select5.ClearScreen)
	fmt.Printf(select5.MoveTo, 1, 0)
	fmt.Print("[RESULT]")
	for i, s := range strings.Split(res, "\n") {
		if i != 0 {
			fmt.Fprint(t, "\n")
		}
		fmt.Printf(select5.MoveTo, i+2, 0)
		fmt.Print(s)
		fmt.Fprint(t, s)
	}
	println()
	fmt.Printf(select5.ClearScreenFromCursor)
}

Enjoy it!

Author

Select5 contributors

Documentation

Index

Constants

View Source
const (
	ClearScreen           = "\x1b[2J"     // Clear entire screen use with print functions
	ClearScreenFromCursor = "\x1b[J"      // Clear all character after the cursor in the current line
	ClearLine             = "\x1b[2K"     // Clear current line with print functions
	ClearLineFromCursor   = "\x1b[K"      // Clear all character after the cursor in the current line
	ResetCursor           = "\x1b[H"      // Move cursor to top-left cursor position
	CursorUp              = "\x1b[1A"     // Move cursor up one line
	CursorDown            = "\x1b[1B"     // Move cursor down one line
	CursorRight           = "\x1b[1C"     // Move cursor right one character
	CursorLeft            = "\x1b[1D"     // Move cursor left one character
	HideCursor            = "\x1b[?25l"   // Hide cursor with print functions
	ShowCursor            = "\x1b[?25h"   // Show cursor with print functions
	MoveTo                = "\x1b[%d;%dH" // Move cursor to position with fmt.Printf

	BS       = 0x08
	ENTER    = 0x0a
	ESC      = 0x1b
	DEL      = 0x7f
	CmdLeft  = 0x37
	CmdRight = 0x36
	UP       = 0x1b5b41
	DOWN     = 0x1b5b42
	RIGHT    = 0x1b5b43
	LEFT     = 0x1b5b44
	END      = 0x1b5b46
	HOME     = 0x1b5b48
	PAGEUP   = 0x1b357e
	PAGEDOWN = 0x1b367e

	CtrlA = 0x01
	CtrlB = 0x01
	CtrlC = 0x03
	CtrlD = 0x04
	CtrlE = 0x05
	CtrlF = 0x06
	CtrlG = 0x07
	CtrlH = 0x08

	CtrlJ = 0x0a
	CtrlK = 0x0b

	CtrlN = 0x0e
	CtrlO = 0x0f
	CtrlP = 0x10

	CtrlS = 0x13

	CtrlU = 0x15

	CtrlX = 0x18
	CtrlY = 0x19
	CtrlZ = 0x1a

	NonUtf8UpperBits = 0x80
)

Terminal control escape sequences

View Source
const (
	IsString byte = 0x01
	IsInt    byte = 0x02
	IsInt8   byte = 0x02
	IsInt16  byte = 0x02

	IsInt32   byte = 0x02
	IsInt64   byte = 0x04
	IsFloat32 byte = 0x08
	IsFloat64 byte = 0x10
	IsBool    byte = 0x20
	IsPointer byte = 0x40
	IsAny     byte = 0x7f
	IsList    byte = 0x00
	IsTable   byte = 0x80
)

Variables

This section is empty.

Functions

func CaptureKeyboardEvents

func CaptureKeyboardEvents() (chan KeyEvent, chan os.Signal)

CaptureKeyboardEvents starts capturing keyboard events in a background goroutine. Returns a channel that delivers KeyEvent structs. The channel should be properly handled and the goroutine will exit when the channel is closed.

func CheckPrimitive added in v0.1.3

func CheckPrimitive(s any) (res byte)

func GetB

func GetB(v any) (bool, error)

GetB extracts a bool value from an any type. Returns an error if the value cannot be converted to a bool.

func GetF32

func GetF32(v any) (float32, error)

GetF32 extracts an int value from an any type. Returns an error if the value cannot be converted to an int32.

func GetF64

func GetF64(v any) (float64, error)

GetF64 extracts an int value from an any type. Returns an error if the value cannot be converted to an int64.

func GetI

func GetI(v any) (int, error)

GetI extracts an int value from an any type. Returns an error if the value cannot be converted to an int.

func GetI16

func GetI16(v any) (int16, error)

GetI16 extracts an int value from an any type. Returns an error if the value cannot be converted to an int16.

func GetI32

func GetI32(v any) (int32, error)

GetI32 extracts an int value from an any type. Returns an error if the value cannot be converted to an int32.

func GetI64

func GetI64(v any) (int64, error)

GetI64 extracts an int value from an any type. Returns an error if the value cannot be converted to an int64.

func GetI8

func GetI8(v any) (int8, error)

GetI8 extracts an int value from an any type. Returns an error if the value cannot be converted to an int8.

func GetS

func GetS(v any) (string, error)

GetS extracts a string value from an any type. Returns an error if the value cannot be converted to a string.

func GetV

func GetV(v any) (string, error)

GetV is a generic extract function for interface values. Returns the value as string if it can be converted, or an error otherwise.

func GetVP added in v0.1.3

func GetVP(v any) (string, error)

GetVP is a generic extract function for interface values for pointer types. Returns the value as string if it can be converted, or an error otherwise.

func RenderMenu

func RenderMenu(list []string, selectedIndex int, prevIndex int)

RenderMenu draws the menu with the current selection (internal use)

func RenderTable

func RenderTable(list [][]any, selectedIndex int) error

RenderTable draws the table with a row cursor. (internal use)

func SelectString

func SelectString(list []string) (string, error)

SelectString presents a list of strings for selection and returns the selected string. It displays an interactive cursor that can be moved with arrow keys. Returns the selected string or an error if: - the provided slice is empty - the keyboard event channel closes - the user quits (q or Ctrl+C)

func SelectTableRow

func SelectTableRow(list [][]any) ([]any, error)

SelectTableRow presents a table of mixed data types for selection and returns the selected row. Each row can contain different data types (string, int, float, bool, etc.). Returns the selected row as []any or an error if: - the provided slice is empty - the keyboard event channel closes - the user quits (q or Ctrl+C)

Types

type CursorPosition added in v0.1.3

type CursorPosition struct {
	X, Y int
}

CursorPosition represents the X,Y coordinates of the cursor in the editor

type Editor added in v0.1.3

type Editor struct {
	Cursor CursorPosition
	In     io.Reader
	Out    io.Writer
	Line   []string
}

Editor provides a simple terminal-based text editor

func NewEditor added in v0.1.3

func NewEditor() *Editor

NewEditor creates a new Editor instance with default settings

func (*Editor) Down added in v0.1.3

func (e *Editor) Down()

Down moves the cursor down one line, adjusting X position if needed

func (*Editor) Edit added in v0.1.3

func (e *Editor) Edit() string

Edit starts the editing session and returns the edited text when complete (with Ctrl-D)

func (*Editor) GetCurrentLine added in v0.1.3

func (e *Editor) GetCurrentLine() string

GetCurrentLine returns the text of the current line

func (*Editor) GetCurrentLineLength added in v0.1.3

func (e *Editor) GetCurrentLineLength() int

GetCurrentLineLength returns the length of the current line in bytes

func (*Editor) GetLineMaxX added in v0.1.3

func (e *Editor) GetLineMaxX() int

GetLineMaxX returns the maximum valid X position for the current line

func (*Editor) GetLineVisibleLength added in v0.1.3

func (e *Editor) GetLineVisibleLength() int

GetLineVisibleLength returns the visible line length on the console. It can be used to calculate the real width for the line, which consists of multibyte (and double-width) characters.

func (*Editor) GetLineVisibleXPosition added in v0.1.3

func (e *Editor) GetLineVisibleXPosition() int

GetLineVisibleXPosition returns the visible cursor position on the console. It can be used to calculate the real cursor position for the line, which consists of multibyte (and double-width) characters.

func (*Editor) GetTextMaxY added in v0.1.3

func (e *Editor) GetTextMaxY() int

GetTextMaxY returns the maximum valid Y position (last line index)

func (*Editor) GoToLineHead added in v0.1.3

func (e *Editor) GoToLineHead()

GoToLineHead moves the cursor to the beginning of the current line

func (*Editor) IsDocumentHead added in v0.1.3

func (e *Editor) IsDocumentHead() bool

IsDocumentHead returns true if the cursor at the beginning of the document

func (*Editor) IsNotOnLastLine added in v0.1.3

func (e *Editor) IsNotOnLastLine() bool

IsNotOnLastLine returns true if the cursor is not on the last line

func (*Editor) IsOnBlankLine added in v0.1.3

func (e *Editor) IsOnBlankLine() bool

IsOnBlankLine returns true if the current line is empty

func (*Editor) IsOnLineEnd added in v0.1.3

func (e *Editor) IsOnLineEnd() bool

IsOnLineEnd returns true if the cursor is at the end of a line

func (*Editor) IsOnLineHead added in v0.1.3

func (e *Editor) IsOnLineHead() bool

IsOnLineHead returns true if the cursor is at the beginning of a line

func (*Editor) IsOnUtf8Boundary added in v0.1.3

func (e *Editor) IsOnUtf8Boundary() bool

IsOnUtf8Boundary returns true if the cursor is on a valid UTF-8 character boundary

func (*Editor) Left added in v0.1.3

func (e *Editor) Left()

Left moves the cursor one position to the right, respecting UTF-8 boundaries

func (*Editor) PutBackspace added in v0.1.3

func (e *Editor) PutBackspace()

PutBackspace removes the previous character at the current cursor position. If it is performed at the beginning of a line, two lines are concatenated to one.

func (*Editor) PutDelete added in v0.1.3

func (e *Editor) PutDelete()

PutDelete removes the character at the current cursor position

func (*Editor) PutEnter added in v0.1.3

func (e *Editor) PutEnter()

PutEnter inserts a new line at the current cursor position

func (*Editor) PutS added in v0.1.3

func (e *Editor) PutS(key []byte)

PutS inserts a string at the current cursor position

func (*Editor) Reposition added in v0.1.3

func (e *Editor) Reposition()

Reposition moves the terminal cursor to match the editor's cursor position

func (*Editor) Right added in v0.1.3

func (e *Editor) Right()

Right moves the cursor one position to the right, respecting UTF-8 boundaries

func (*Editor) Up added in v0.1.3

func (e *Editor) Up()

Up moves the cursor up one line, adjusting X position if needed

type KeyEvent

type KeyEvent struct {
	Key         rune   // The character pressed
	Code        int    // Numeric key code
	Ctrl        bool   // Whether Ctrl was pressed
	Alt         bool   // Whether Alt was pressed
	Shift       bool   // Whether Shift was pressed
	Special     int    // Special key name (UP, DOWN, ENTER, etc.)
	IsRuneStart bool   // Whether the character is UTF-8 multibyte character or not
	Runes       []byte // Raw key bytes
}

KeyEvent represents a keyboard input event with information about special keys and modifiers.

func (KeyEvent) Size added in v0.1.3

func (e KeyEvent) Size() (size int)

Size presents the size in octet order of the UTF-8 character.

func (KeyEvent) Utf8Char added in v0.1.3

func (e KeyEvent) Utf8Char() ([]byte, error)

Utf8Char returns byte representation for the UTF-8 character. If it is an ascii character, simply return the first byte.

type Selector added in v0.1.3

type Selector struct {
	Header []string // selection header
	Data   any
}

Selector represents a selectable dataset with an optional header

func NewSelectorFrom added in v0.1.3

func NewSelectorFrom(p []any) *Selector

NewSelectorFrom creates a new Selector from a slice of any type

func (*Selector) Select added in v0.1.3

func (s *Selector) Select() (any, error)

Select performs the selection based on the data type. Returns the selected item or an error if selection is not supported

func (*Selector) Type added in v0.1.3

func (s *Selector) Type() byte

Type determines the type of data in the selector Returns a byte value representing the data type(s).

Directories

Path Synopsis
example
simple command
table command
textinput command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL