<commit_before>package main

import (
	"fmt"
	"github.com/aqatl/mal/mal"
	"github.com/jroimartin/gocui"
	"github.com/urfave/cli"
	"strings"
	"github.com/sahilm/fuzzy"
)

func fuzzySelectEntry(ctx *cli.Context) error {
	gui, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return fmt.Errorf("gocui error: %v", err)
	}

	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	listStr := make([]string, len(list))
	for i, entry := range list {
		listStr[i] = strings.ToLower(fmt.Sprintf("%s (%s)", entry.Title, entry.Synonyms))
	}

	fsg := &fuzzySelGui{
		List: list,
		ListStr: listStr,
		Cfg:  cfg,
		Match: nil,
	}

	gui.SetManager(fsg)
	setGuiKeyBindings(gui, fsg)

	gui.Cursor = true
	gui.Mouse = false
	gui.Highlight = true
	gui.SelFgColor = gocui.ColorGreen

	err = gui.MainLoop()
	if err != nil && err != gocui.ErrQuit {
		gui.Close()
		return err
	}

	if fsg.Match == nil {
		gui.Close()
		return nil
	}

	cfg.SelectedID = fsg.Match.ID
	cfg.Save()

	gui.Close()

	fmt.Println("Selected entry:")
	printEntryDetails(fsg.Match)

	return nil
}

const (
	FsgInputView  = "fsgInputView"
	FsgOutputView = "fsgOutputView"
	FsgShortcutsView = "fsgShortcutsView"
)

type fuzzySelGui struct {
	List mal.AnimeList
	ListStr []string
	Cfg  *Config

	Matches []fuzzy.Match
	Match *mal.Anime

	InputView, OutputView *gocui.View
}

func (fsg *fuzzySelGui) Layout(gui *gocui.Gui) error {
	w, h := gui.Size()
	if v, err := gui.SetView(FsgInputView, 0, 0, w-1, 2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Input"
		v.Editor = gocui.EditorFunc(fsg.InputViewEditor)
		v.Editable = true
		v.Wrap = true

		gui.SetCurrentView(FsgInputView)
		fsg.InputView = v
	}

	if v, err := gui.SetView(FsgOutputView, 0, 3, w-1, h-4); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Found entries"
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack
		v.Highlight = true
		v.Editable = true
		v.Editor = gocui.EditorFunc(fsg.OutputViewEditor)

		fsg.OutputView = v
	}

	if v, err := gui.SetView(FsgShortcutsView, 0, h-3, w-1, h-1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Shortcuts"
		v.Editable = false

		fmt.Fprintf(v, "Ctrl+C: quit | Tab: switch window | Enter: select highlighted entry")
	}

	return nil
}

func (fsg *fuzzySelGui) InputViewEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
	gocui.DefaultEditor.Edit(v, key, ch, mod)

	fsg.OutputView.Clear()

	pattern := strings.TrimSpace(v.Buffer())

	fsg.Matches = fuzzy.Find(pattern, fsg.ListStr)
	for _, match := range fsg.Matches {
		fmt.Fprintf(fsg.OutputView, "%s\n", fsg.List[match.Index].Title)
	}
}

func (fsg *fuzzySelGui) OutputViewEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
	switch key {
	case gocui.KeyArrowDown:
		v.MoveCursor(0, 1, false)
	case gocui.KeyArrowUp:
		v.MoveCursor(0, -1, false)
	}
}

func setGuiKeyBindings(gui *gocui.Gui, fsg *fuzzySelGui) {
	quit := func(gui *gocui.Gui, v *gocui.View) error {
		return gocui.ErrQuit
	}
	gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)
	gui.SetKeybinding("", gocui.KeyCtrlQ, gocui.ModNone, quit)

	gui.SetKeybinding("", gocui.KeyTab, gocui.ModNone, func(gui *gocui.Gui, v *gocui.View) error {
		switch v.Name() {
		case FsgInputView:
			gui.SetCurrentView(FsgOutputView)
		case FsgOutputView:
			gui.SetCurrentView(FsgInputView)
		}
		return nil
	})

	gui.SetKeybinding(FsgOutputView, gocui.KeyEnter, gocui.ModNone, func(gui *gocui.Gui, v *gocui.View) error {
		_, y := fsg.OutputView.Cursor()
		if ml := len(fsg.Matches); ml == 0 || y > ml - 1 || y < 0 {
			return nil
		}

		fsg.Match = fsg.List[fsg.Matches[y].Index]

		return gocui.ErrQuit
	})
}<commit_msg>Highlighting matched characters in fuzzy select<commit_after>package main

import (
	"bufio"
	"fmt"
	"github.com/aqatl/mal/mal"
	"github.com/fatih/color"
	"github.com/jroimartin/gocui"
	"github.com/sahilm/fuzzy"
	"github.com/urfave/cli"
	"strings"
)

func fuzzySelectEntry(ctx *cli.Context) error {
	gui, err := gocui.NewGui(gocui.OutputNormal)
	if err != nil {
		return fmt.Errorf("gocui error: %v", err)
	}

	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	listStr := make([]string, len(list))
	for i, entry := range list {
		listStr[i] = strings.ToLower(fmt.Sprintf("%s %s",
			entry.Title,
			strings.Replace(entry.Synonyms, ";", "", -1)))
	}

	fsg := &fuzzySelGui{
		List:    list,
		ListStr: listStr,
		Cfg:     cfg,
		Match:   nil,
	}

	gui.SetManager(fsg)
	setGuiKeyBindings(gui, fsg)

	gui.Cursor = true
	gui.Mouse = false
	gui.Highlight = true
	gui.SelFgColor = gocui.ColorGreen

	err = gui.MainLoop()
	if err != nil && err != gocui.ErrQuit {
		gui.Close()
		return err
	}

	if fsg.Match == nil {
		gui.Close()
		return nil
	}

	cfg.SelectedID = fsg.Match.ID
	cfg.Save()

	gui.Close()

	fmt.Println("Selected entry:")
	printEntryDetails(fsg.Match)

	return nil
}

const (
	FsgInputView     = "fsgInputView"
	FsgOutputView    = "fsgOutputView"
	FsgShortcutsView = "fsgShortcutsView"
)

type fuzzySelGui struct {
	List    mal.AnimeList
	ListStr []string
	Cfg     *Config

	Matches []fuzzy.Match
	Match   *mal.Anime

	InputView, OutputView *gocui.View
}

func (fsg *fuzzySelGui) Layout(gui *gocui.Gui) error {
	w, h := gui.Size()
	if v, err := gui.SetView(FsgInputView, 0, 0, w-1, 2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Input"
		v.Editor = gocui.EditorFunc(fsg.InputViewEditor)
		v.Editable = true
		v.Wrap = true

		gui.SetCurrentView(FsgInputView)
		fsg.InputView = v
	}

	if v, err := gui.SetView(FsgOutputView, 0, 3, w-1, h-4); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Found entries"
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack
		v.Highlight = true
		v.Editable = true
		v.Editor = gocui.EditorFunc(fsg.OutputViewEditor)

		fsg.OutputView = v
	}

	if v, err := gui.SetView(FsgShortcutsView, 0, h-3, w-1, h-1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Shortcuts"
		v.Editable = false

		fmt.Fprintf(v, "Ctrl+C: quit | Tab: switch window | Enter: select highlighted entry")
	}

	return nil
}

func (fsg *fuzzySelGui) InputViewEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
	gocui.DefaultEditor.Edit(v, key, ch, mod)

	fsg.OutputView.Clear()

	pattern := strings.TrimSpace(v.Buffer())
	fsg.Matches = fuzzy.Find(pattern, fsg.ListStr)

	highlight := color.New(color.FgBlack, color.BgWhite).FprintFunc()
	buf := bufio.NewWriter(fsg.OutputView)

	for _, match := range fsg.Matches {
		mIdx := 0
		for i, r := range []rune(fsg.List[match.Index].Title) {
			if mIdx < len(match.MatchedIndexes) && i == match.MatchedIndexes[mIdx] {
				mIdx++
				highlight(buf, string(r))
			} else {
				buf.WriteRune(r)
			}
		}
		buf.WriteRune('\n')
	}
	buf.Flush()
}

func (fsg *fuzzySelGui) OutputViewEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
	switch key {
	case gocui.KeyArrowDown:
		v.MoveCursor(0, 1, false)
	case gocui.KeyArrowUp:
		v.MoveCursor(0, -1, false)
	}
}

func setGuiKeyBindings(gui *gocui.Gui, fsg *fuzzySelGui) {
	quit := func(gui *gocui.Gui, v *gocui.View) error {
		return gocui.ErrQuit
	}
	gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit)
	gui.SetKeybinding("", gocui.KeyCtrlQ, gocui.ModNone, quit)

	gui.SetKeybinding("", gocui.KeyTab, gocui.ModNone, func(gui *gocui.Gui, v *gocui.View) error {
		switch v.Name() {
		case FsgInputView:
			gui.SetCurrentView(FsgOutputView)
		case FsgOutputView:
			gui.SetCurrentView(FsgInputView)
		}
		return nil
	})

	gui.SetKeybinding(FsgOutputView, gocui.KeyEnter, gocui.ModNone, func(gui *gocui.Gui, v *gocui.View) error {
		_, y := fsg.OutputView.Cursor()
		if ml := len(fsg.Matches); ml == 0 || y > ml-1 || y < 0 {
			return nil
		}

		fsg.Match = fsg.List[fsg.Matches[y].Index]

		return gocui.ErrQuit
	})
}
{'fraction_non_alphanumeric': 0.14739086803813348, 'fraction_numerical': 0.005017561465127948, 'mean_word_length': 3.619351100811124, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1249196', 'n_tokens_mistral': 3604, 'n_tokens_neox': 3315, 'n_words': 684}