package ui import ( "context" "fmt" "log/slog" "sync" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "github.com/urfave/cli/v2" "git.gabrielgio.me/dict/db" ) const ( memory = ":memory:" ) var UICommand = &cli.Command{ Name: "ui", Usage: "interactive dictionary", Flags: []cli.Flag{ &cli.StringFlag{ Name: "database", Value: "main.dict", Usage: "Dictionary database location", }, }, Action: func(cCtx *cli.Context) error { name := cCtx.String("database") return Run(context.Background(), name) }, } func Run(ctx context.Context, name string) error { db, err := db.Open(memory) if err != nil { return err } err = db.Restore(ctx, name) if err != nil { return err } textView := tview.NewTextView(). SetDynamicColors(true). SetRegions(true) app := tview.NewApplication() search := "" tx := sync.Mutex{} input := tview.NewInputField(). SetChangedFunc(func(v string) { tx.Lock() defer tx.Unlock() search = v go func(v string) { words, err := db.SelectDict(ctx, v, 100) if err != nil { return } tx.Lock() if search != v { tx.Unlock() return } tx.Unlock() textView.Clear() lastWord := "" for _, w := range words { w.Word = tview.Escape(w.Word) w.Line = tview.Escape(w.Line) if lastWord == w.Word { fmt.Fprintf(textView, "%s\n", w.Line) } else if lastWord == "" { fmt.Fprintf(textView, "[::bu]%s[-:-:-]\n", w.Word) fmt.Fprintf(textView, "%s\n", w.Line) } else { fmt.Fprintf(textView, "\n[::bu]%s[-:-:-]\n", w.Word) fmt.Fprintf(textView, "%s\n", w.Line) } lastWord = w.Word } app.Draw() }(v) }). SetAutocompleteFunc(func(v string) []string { if len(v) == 0 { return []string{} } vs, err := db.SelectSpell(ctx, v) if err != nil { slog.Error("Error select spelling", "error", err) return []string{} } return vs }) input.SetDoneFunc(func(key tcell.Key) { if key == tcell.KeyEscape { textView.Clear() input.SetText("") } }) grid := tview.NewGrid(). SetRows(1, 0, 3). AddItem(input, 0, 0, 1, 3, 0, 0, false). AddItem(textView, 1, 0, 1, 3, 0, 0, false) return app. SetRoot(grid, true). SetFocus(input). Run() }