diff --git a/tools/cmd/hints/main.go b/tools/cmd/hints/main.go index 8d7393417..53abb1e7c 100644 --- a/tools/cmd/hints/main.go +++ b/tools/cmd/hints/main.go @@ -124,8 +124,8 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { tui.ReportError(fmt.Errorf("Extra command line arguments present: %s", strings.Join(args, " "))) return 1, nil } - text := parse_input(utils.UnsafeBytesToString(stdin)) - all_marks, index_map, err := find_marks(text, o) + input_text := parse_input(utils.UnsafeBytesToString(stdin)) + text, all_marks, index_map, err := find_marks(input_text, o) if err != nil { tui.ReportError(err) return 1, nil @@ -141,7 +141,6 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { alphabet = DEFAULT_HINT_ALPHABET } ignore_mark_indices := utils.NewSet[int](8) - _, _, _ = all_marks, index_map, ignore_mark_indices window_title := o.WindowTitle if window_title == "" { switch o.Type { @@ -173,30 +172,31 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { hint_style := fctx.SprintFunc(fmt.Sprintf("fg=%s bg=%s bold", o.HintsForegroundColor, o.HintsBackgroundColor)) text_style := fctx.SprintFunc(fmt.Sprintf("fg=bright-%s bold", o.HintsTextColor)) - highlight_mark := func(m *Mark) string { + highlight_mark := func(m *Mark, mark_text string) string { hint := encode_hint(m.Index, alphabet) if current_input != "" && !strings.HasPrefix(hint, current_input) { - return faint(text) + return faint(mark_text) } hint = hint[len(current_input):] if hint == "" { hint = " " } - text = text[len(hint):] - return hint_style(hint) + text_style(text) + mark_text = mark_text[len(hint):] + return hint_style(hint) + text_style(mark_text) } render := func() string { + ans := text for i := len(all_marks) - 1; i >= 0; i-- { mark := &all_marks[i] if ignore_mark_indices.Has(mark.Index) { continue } - mtext := highlight_mark(mark) - text = text[:mark.Start] + mtext + text[mark.End:] + mtext := highlight_mark(mark, ans[mark.Start:mark.End]) + ans = ans[:mark.Start] + mtext + ans[mark.End:] } - text = strings.ReplaceAll(text, "\x00", "") - return strings.TrimRightFunc(strings.NewReplacer("\r", "\r\n", "\n", "\r\n").Replace(text), unicode.IsSpace) + ans = strings.ReplaceAll(ans, "\x00", "") + return strings.TrimRightFunc(strings.NewReplacer("\r", "\r\n", "\n", "\r\n").Replace(ans), unicode.IsSpace) } draw_screen := func() { @@ -208,6 +208,10 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { lp.ClearScreen() lp.QueueWriteString(current_text) } + reset := func() { + current_input = "" + current_text = "" + } lp.OnInitialize = func() (string, error) { lp.SendOverlayReady() @@ -225,7 +229,89 @@ func main(_ *cli.Command, o *Options, args []string) (rc int, err error) { draw_screen() return nil } + lp.OnText = func(text string, _, _ bool) error { + changed := false + for _, ch := range text { + if strings.ContainsRune(alphabet, ch) { + current_input += string(ch) + changed = true + } + } + if changed { + matches := []*Mark{} + for idx, m := range index_map { + if eh := encode_hint(idx, alphabet); strings.HasPrefix(eh, current_input) { + matches = append(matches, m) + } + } + if len(matches) == 1 { + chosen = append(chosen, matches[0]) + if o.Multiple { + ignore_mark_indices.Add(matches[0].Index) + reset() + } else { + lp.Quit(0) + return nil + } + } + current_text = "" + draw_screen() + } + return nil + } + lp.OnKeyEvent = func(ev *loop.KeyEvent) error { + if ev.MatchesPressOrRepeat("backspace") { + ev.Handled = true + r := []rune(current_input) + if len(r) > 0 { + r = r[:len(r)-1] + current_input = string(r) + current_text = "" + } + draw_screen() + } else if ev.MatchesPressOrRepeat("enter") || ev.MatchesPressOrRepeat("space") { + ev.Handled = true + if current_input != "" { + idx := decode_hint(current_input, alphabet) + if m := index_map[idx]; m != nil { + chosen = append(chosen, m) + ignore_mark_indices.Add(idx) + if o.Multiple { + reset() + draw_screen() + } else { + lp.Quit(0) + } + } else { + current_input = "" + current_text = "" + draw_screen() + } + } + } else if ev.MatchesPressOrRepeat("esc") { + if o.Multiple { + lp.Quit(1) + } else { + lp.Quit(0) + } + } + return nil + } + + err = lp.Run() + if err != nil { + return 1, err + } + ds := lp.DeathSignalName() + if ds != "" { + fmt.Println("Killed by signal: ", ds) + lp.KillIfSignalled() + return 1, nil + } + if lp.ExitCode() != 0 { + return lp.ExitCode(), nil + } result.Match = make([]string, len(chosen)) result.Groupdicts = make([]map[string]string, len(chosen)) for i, m := range chosen { diff --git a/tools/cmd/hints/marks.go b/tools/cmd/hints/marks.go index 168deea4a..98bc11f04 100644 --- a/tools/cmd/hints/marks.go +++ b/tools/cmd/hints/marks.go @@ -322,31 +322,32 @@ func (self *ErrNoMatches) Error() string { return fmt.Sprintf("No %s found", none_of) } -func find_marks(text string, opts *Options) (ans []Mark, index_map map[int]*Mark, err error) { - text, hyperlinks := process_escape_codes(text) +func find_marks(text string, opts *Options) (sanitized_text string, ans []Mark, index_map map[int]*Mark, err error) { + sanitized_text, hyperlinks := process_escape_codes(text) pattern, post_processors, group_processors := functions_for(opts) if opts.Type == "hyperlink" { ans = hyperlinks } else { r, err := regexp.Compile(pattern) if err != nil { - return nil, nil, fmt.Errorf("Failed to compile the regex pattern: %#v with error: %w", pattern, err) + return "", nil, nil, fmt.Errorf("Failed to compile the regex pattern: %#v with error: %w", pattern, err) } - ans = mark(r, post_processors, group_processors, text, opts) + ans = mark(r, post_processors, group_processors, sanitized_text, opts) } if len(ans) == 0 { - return nil, nil, &ErrNoMatches{Type: opts.Type} + return "", nil, nil, &ErrNoMatches{Type: opts.Type} } largest_index := ans[len(ans)-1].Index offset := utils.Max(0, opts.HintsOffset) index_map = make(map[int]*Mark, len(ans)) - for _, m := range ans { + for i := range ans { + m := &ans[i] if opts.Ascending { m.Index += offset } else { m.Index = largest_index - m.Index + offset } - index_map[m.Index] = &m + index_map[m.Index] = m } return } diff --git a/tools/cmd/hints/marks_test.go b/tools/cmd/hints/marks_test.go index 9b28aba48..bf8eba2d5 100644 --- a/tools/cmd/hints/marks_test.go +++ b/tools/cmd/hints/marks_test.go @@ -21,7 +21,7 @@ func TestHintMarking(t *testing.T) { cols := 20 r := func(text string, url ...string) { ptext := convert_text(text, cols) - marks, _, err := find_marks(ptext, opts) + _, marks, _, err := find_marks(ptext, opts) if err != nil { var e *ErrNoMatches if len(url) != 0 || !errors.As(err, &e) { @@ -51,7 +51,7 @@ func TestHintMarking(t *testing.T) { opts.Type = "linenum" m := func(text, path string, line int) { ptext := convert_text(text, cols) - marks, _, err := find_marks(ptext, opts) + _, marks, _, err := find_marks(ptext, opts) if err != nil { t.Fatalf("%#v failed with error: %s", text, err) }