Created
June 3, 2024 12:59
-
-
Save bechampion/d12ee2f65c5339f3cabdfab09532984d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"math/rand" | |
"os" | |
"strconv" | |
"strings" | |
tea "github.com/charmbracelet/bubbletea" | |
"github.com/charmbracelet/lipgloss" | |
"github.com/google/uuid" | |
"github.com/sahilm/fuzzy" | |
) | |
var ( | |
left = [...]string{ | |
"admiring", "adoring", "affectionate", "agitated", "amazing", "angry", "awesome", "beautiful", | |
"blissful", "bold", "boring", "brave", "busy", "charming", "clever", "compassionate", "competent", | |
"condescending", "confident", "cool", "cranky", "crazy", "dazzling", "determined", "distracted", | |
"dreamy", "eager", "ecstatic", "elastic", "elated", "elegant", "eloquent", "epic", "exciting", | |
"fervent", "festive", "flamboyant", "focused", "friendly", "frosty", "funny", "gallant", "gifted", | |
"goofy", "gracious", "great", "happy", "hardcore", "heuristic", "hopeful", "hungry", "infallible", | |
"inspiring", "intelligent", "interesting", "jolly", "jovial", "keen", "kind", "laughing", "loving", | |
"lucid", "magical", "modest", "musing", "mystifying", "naughty", "nervous", "nice", "nifty", | |
"nostalgic", "objective", "optimistic", "peaceful", "pedantic", "pensive", "practical", "priceless", | |
"quirky", "quizzical", "recursing", "relaxed", "reverent", "romantic", "sad", "serene", "sharp", | |
"silly", "sleepy", "stoic", "strange", "stupefied", "suspicious", "sweet", "tender", "thirsty", | |
"trusting", "unruffled", "upbeat", "vibrant", "vigilant", "vigorous", "wizardly", "wonderful", | |
"xenodochial", "youthful", "zealous", "zen", | |
} | |
right = [...]string{ | |
"agnesi", "albattani", "allen", "almeida", "antonelli", "archimedes", "ardinghelli", "aryabhata", | |
"austin", "babbage", "banach", "banzai", "bardeen", "bartik", "bassi", "beaver", "bell", "benz", | |
"bhabha", "bhaskara", "black", "blackburn", "blackwell", "bohr", "booth", "borg", "bose", "bouman", | |
"boyd", "brahmagupta", "brattain", "brown", "buck", "burnell", "cannon", "carson", "cartwright", | |
"carver", "cerf", "chandrasekhar", "chaplygin", "chatelet", "chatterjee", "chaum", "chebyshev", | |
"clarke", "cohen", "colden", "cori", "cray", "curie", "lovelace", "lumiere", "mahavira", "margulis", | |
"maxwell", "mayer", "mccarthy", "mcclintock", "mclaren", "mclean", "mcnulty", "meitner", "mendel", | |
"mendeleev", "meninsky", "merkle", "mestorf", "mirzakhani", "montalcini", "moore", "morse", "moser", | |
"murdock", "napier", "nash", "neumann", "newton", "nightingale", "nobel", "noether", "northcutt", | |
"noyce", "panini", "pare", "pascal", "pasteur", "payne", "perlman", "pike", "poincare", "poitras", | |
"proskuriakova", "ptolemy", "raman", "ramanujan", "rhodes", "ride", "ritchie", "robinson", "roentgen", | |
"rosalind", "rubin", "saha", "sammet", "sanderson", "satoshi", "shamir", "shannon", "shaw", "shirley", | |
"shockley", "shtern", "sinoussi", "snyder", "solomon", "spence", "stonebraker", "sutherland", "swanson", | |
"swartz", "swirles", "taussig", "tesla", "tharp", "thompson", "torvalds", "tu", "turing", "varahamihira", | |
"vaughan", "villani", "visvesvaraya", "volhard", "wescoff", "wilbur", "wiles", "williams", "williamson", | |
"wilson", "wing", "wozniak", "wright", "wu", "yalow", "yonath", "zhukovsky", | |
} | |
) | |
func GetRandomName(retry int) string { | |
begin: | |
name := left[rand.Intn(len(left))] + "_" + right[rand.Intn(len(right))] //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) | |
if name == "boring_wozniak" /* Steve Wozniak is not boring */ { | |
goto begin | |
} | |
if retry > 0 { | |
name += strconv.Itoa(rand.Intn(10)) //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) | |
} | |
return name | |
} | |
func GiveMeNames(len int) []string { | |
var response []string | |
for i := 0; i < len; i++ { | |
response = append(response, GetRandomName(1)) | |
} | |
return response | |
} | |
var dockerInstanceNames = GiveMeNames(1000) | |
type match struct { | |
text string | |
score int | |
} | |
type model struct { | |
input string | |
cursor int | |
suggestions []match | |
selected int | |
width int | |
height int | |
} | |
func initialModel() model { | |
return model{ | |
input: "", | |
cursor: 0, | |
suggestions: getSuggestions(""), | |
selected: -1, | |
} | |
} | |
func getSuggestions(input string) []match { | |
var suggestions []match | |
if input == "" { | |
for _, name := range dockerInstanceNames { | |
suggestions = append(suggestions, match{text: name, score: 100}) | |
} | |
return suggestions | |
} | |
matches := fuzzy.Find(input, dockerInstanceNames) | |
for _, matchx := range matches { | |
score := calculateMatchScore(matchx.MatchedIndexes, len(input), len(dockerInstanceNames[matchx.Index])) | |
suggestions = append(suggestions, match{text: dockerInstanceNames[matchx.Index], score: score}) | |
} | |
return suggestions | |
} | |
func calculateMatchScore(matchedIndexes []int, inputLength, textLength int) int { | |
return len(matchedIndexes) * 100 / textLength | |
} | |
func (m model) Init() tea.Cmd { | |
return nil | |
} | |
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | |
switch msg := msg.(type) { | |
case tea.KeyMsg: | |
switch msg.String() { | |
case "ctrl+c", "esc": | |
return m, tea.Quit | |
case "ctrl+l": | |
fmt.Print("\033[H\033[2J") | |
m.input = "" | |
case "ctrl+w": | |
m.input = deleteLastWord(m.input) | |
case "backspace": | |
if len(m.input) > 0 { | |
m.input = m.input[:len(m.input)-1] | |
} | |
case "enter": | |
return m, tea.Quit | |
case "up": | |
if m.selected > 0 { | |
m.selected-- | |
} | |
case "ctrl+k": | |
if m.selected > 0 { | |
m.selected-- | |
} | |
case "ctrl+j": | |
if m.selected < len(m.suggestions)-1 { | |
m.selected++ | |
} | |
case "ctrl+u": | |
if m.selected > 0 { | |
m.selected-- | |
} | |
case "ctrl+d": | |
if m.selected < len(m.suggestions)-1 { | |
m.selected++ | |
} | |
case "down": | |
if m.selected < len(m.suggestions)-1 { | |
m.selected++ | |
} | |
default: | |
m.input += msg.String() | |
} | |
m.suggestions = getSuggestions(m.input) | |
if m.selected >= len(m.suggestions) { | |
m.selected = len(m.suggestions) - 1 | |
} | |
case tea.WindowSizeMsg: | |
m.width = msg.Width | |
m.height = msg.Height | |
} | |
return m, nil | |
} | |
func deleteLastWord(input string) string { | |
if len(input) == 0 { | |
return input | |
} | |
words := strings.Fields(input) | |
if len(words) == 0 { | |
return "" | |
} | |
return strings.Join(words[:len(words)-1], " ") + " " | |
} | |
func greenText(text string, index int) string { | |
colors := []lipgloss.Color{ | |
lipgloss.Color("#006400"), // Dark Green | |
lipgloss.Color("#008000"), // Green | |
lipgloss.Color("#228B22"), // Forest Green | |
lipgloss.Color("#32CD32"), // Lime Green | |
lipgloss.Color("#3CB371"), // Medium Sea Green | |
lipgloss.Color("#66CDAA"), // Medium Aquamarine | |
lipgloss.Color("#7CFC00"), // Lawn Green | |
} | |
var coloredText strings.Builder | |
for i, char := range text { | |
color := colors[(index+i)%len(colors)] | |
coloredText.WriteString(lipgloss.NewStyle().Foreground(color).Render(string(char))) | |
} | |
return coloredText.String() | |
} | |
func (m model) View() string { | |
var b strings.Builder | |
// Center the input text on the screen | |
if m.input != "" { | |
screenWidth := m.width | |
padding := (screenWidth - len(m.input)) / 2 | |
b.WriteString(strings.Repeat(" ", padding) + m.input + "\n\n") | |
} | |
// Use 80% of the screen height for the suggestions | |
listHeight := int(float64(m.height) * 0.8) | |
b.WriteString("Start typing: " + m.input + "\n\n") | |
if len(m.suggestions) > 0 { | |
for i, suggestion := range m.suggestions[:min(len(m.suggestions), listHeight)] { | |
cursor := " " // no cursor | |
if m.selected == i { | |
cursor = ">" // cursor | |
} | |
// Generate UUID for each suggestion | |
id := uuid.New() | |
suggestionWithID := fmt.Sprintf("%s %s", suggestion.text, id.String()) | |
line := fmt.Sprintf("%s %3d%% %s", cursor, suggestion.score, greenText(suggestionWithID, i)) | |
if m.selected == i { | |
line = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("205")).Background(lipgloss.Color("235")).Render(line) // Dark gray background | |
} else { | |
line = lipgloss.NewStyle().Render(line) | |
} | |
b.WriteString(line + "\n") | |
} | |
} else { | |
b.WriteString("No suggestions found.\n") | |
} | |
return b.String() | |
} | |
func min(a, b int) int { | |
if a < b { | |
return a | |
} | |
return b | |
} | |
func main() { | |
p := tea.NewProgram(initialModel()) | |
if err := p.Start(); err != nil { | |
fmt.Printf("Error: %v\n", err) | |
os.Exit(1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment