fzy returns which characters matched the search for every item, so use this to highlight them. This defaults to a light blue color that is readable with both black and white (selected) backgrounds. The returned positions are just bytes, so we can't put ASS tags or escape sequences around each byte because that would break displaying multibyte UTF-8 characters. Only put them around sequences of matched positions, and only when the sequences start and end at actual character positions.
Unified · Split
| @@ -218,6 +218,11 @@ Configurable Options · 217 unmodified lines | |||
| 218 | 218 | ||
| 219 | The background color of the selected item. | 219 | The background color of the selected item. |
| 220 | 220 | ||
| 221 | ``match_color`` | ||
| 222 | Default: ``#0088FF`` | ||
| 223 | |||
| 224 | The color of characters that match the searched string. | ||
| 225 | |||
| 221 | ``case_sensitive`` | 226 | ``case_sensitive`` |
| 222 | Default: no on Windows, yes on other platforms. | 227 | Default: no on Windows, yes on other platforms. |
| 223 | 228 | ||
| @@ -42,6 +42,7 @@ local opts = { · 41 unmodified lines | |||
| 42 | scale_with_window = "auto", | 42 | scale_with_window = "auto", |
| 43 | selected_color = '#222222', | 43 | selected_color = '#222222', |
| 44 | selected_back_color = '#FFFFFF', | 44 | selected_back_color = '#FFFFFF', |
| 45 | match_color = '#0088FF', | ||
| 45 | case_sensitive = platform ~= 'windows' and true or false, | 46 | case_sensitive = platform ~= 'windows' and true or false, |
| 46 | history_dedup = true, | 47 | history_dedup = true, |
| 47 | persist_history = false, | 48 | persist_history = false, |
| @@ -77,6 +78,8 @@ local terminal_styles = { · 29 unmodified lines | |||
| 77 | selected_completion = '\027[7m', | 78 | selected_completion = '\027[7m', |
| 78 | default_item = '\027[1m', | 79 | default_item = '\027[1m', |
| 79 | disabled = '\027[38;5;8m', | 80 | disabled = '\027[38;5;8m', |
| 81 | matched_position = '\027[34m', | ||
| 82 | match_end = '\027[39m', | ||
| 80 | } | 83 | } |
| 81 | 84 | ||
| 82 | local repl_active = false | 85 | local repl_active = false |
| @@ -183,6 +186,18 @@ local function len_utf8(str) · 100 unmodified lines | |||
| 183 | return len | 186 | return len |
| 184 | end | 187 | end |
| 185 | 188 | ||
| 189 | local function utf8_positions(str) | ||
| 190 | local pos = 1 | ||
| 191 | local positions = {true} | ||
| 192 | |||
| 193 | while pos <= str:len() do | ||
| 194 | pos = next_utf8(str, pos) | ||
| 195 | positions[pos] = true | ||
| 196 | end | ||
| 197 | |||
| 198 | return positions | ||
| 199 | end | ||
| 200 | |||
| 186 | 201 | ||
| 187 | -- Functions to calculate the font width. | 202 | -- Functions to calculate the font width. |
| 188 | local width_length_ratio = 0.5 | 203 | local width_length_ratio = 0.5 |
| @@ -467,9 +482,7 @@ local function fuzzy_find(needle, haystacks, case_sensitive) · 278 unmodified lines | |||
| 467 | 482 | ||
| 468 | return i[1] < j[1] | 483 | return i[1] < j[1] |
| 469 | end) | 484 | end) |
| 470 | for i, value in ipairs(result) do | 485 | |
| 471 | result[i] = value[1] | ||
| 472 | end | ||
| 473 | return result | 486 | return result |
| 474 | end | 487 | end |
| 475 | 488 | ||
| @@ -480,6 +493,9 @@ local function get_matches_to_print(terminal) · 4 unmodified lines | |||
| 480 | 493 | ||
| 481 | local items = {} | 494 | local items = {} |
| 482 | local max_lines = calculate_max_lines() | 495 | local max_lines = calculate_max_lines() |
| 496 | local escape = terminal and function (str) return str end or ass_escape | ||
| 497 | local highlight = terminal and terminal_styles.matched_position or | ||
| 498 | '{\\1c&H' .. option_color_to_ass(opts.match_color) .. '}' | ||
| 483 | 499 | ||
| 484 | if selected_match < first_match_to_print then | 500 | if selected_match < first_match_to_print then |
| 485 | first_match_to_print = selected_match | 501 | first_match_to_print = selected_match |
| @@ -491,27 +507,68 @@ local function get_matches_to_print(terminal) · 5 unmodified lines | |||
| 491 | #matches) | 507 | #matches) |
| 492 | 508 | ||
| 493 | for i = first_match_to_print, last_match_to_print do | 509 | for i = first_match_to_print, last_match_to_print do |
| 494 | local style = '' | 510 | local item = '' |
| 511 | local end_highlight = terminal and terminal_styles.match_end or '{\\1c}' | ||
| 495 | 512 | ||
| 496 | if terminal then | 513 | if terminal then |
| 497 | if matches[i].index == default_item then | 514 | if matches[i].index == default_item then |
| 498 | style = terminal_styles.default_item | 515 | item = terminal_styles.default_item |
| 499 | end | 516 | end |
| 500 | if i == selected_match then | 517 | if i == selected_match then |
| 501 | style = style .. terminal_styles.selected_completion | 518 | item = item .. terminal_styles.selected_completion |
| 502 | end | 519 | end |
| 503 | else | 520 | else |
| 504 | if i == selected_match then | 521 | if i == selected_match then |
| 505 | if searching_history and | 522 | if searching_history and |
| 506 | mp.get_property('osd-border-style') == 'outline-and-shadow' then | 523 | mp.get_property('osd-border-style') == 'outline-and-shadow' then |
| 507 | style = get_selected_ass() | 524 | item = get_selected_ass() |
| 525 | else | ||
| 526 | item = '{\\1c&H' .. option_color_to_ass(opts.selected_color) .. '&}' | ||
| 527 | end | ||
| 528 | end_highlight = item | ||
| 529 | end | ||
| 530 | end | ||
| 531 | |||
| 532 | local char_positions = utf8_positions(matches[i].text) | ||
| 533 | local start_of_last_match = matches[i].positions[1] | ||
| 534 | |||
| 535 | if not start_of_last_match then | ||
| 536 | item = item .. escape(matches[i].text) | ||
| 537 | elseif start_of_last_match > 1 then | ||
| 538 | item = item .. escape(matches[i].text:sub(1, start_of_last_match - 1)) | ||
| 539 | end | ||
| 540 | |||
| 541 | for j, pos in ipairs(matches[i].positions) do | ||
| 542 | local last_pos = matches[i].positions[j - 1] | ||
| 543 | |||
| 544 | if last_pos and pos > last_pos + 1 then | ||
| 545 | if char_positions[start_of_last_match] and char_positions[last_pos + 1] then | ||
| 546 | item = item .. highlight .. | ||
| 547 | escape(matches[i].text:sub(start_of_last_match, last_pos)) .. | ||
| 548 | end_highlight .. | ||
| 549 | escape(matches[i].text:sub(last_pos + 1, pos - 1)) | ||
| 508 | else | 550 | else |
| 509 | style = '{\\1c&H' .. option_color_to_ass(opts.selected_color) .. '&}' | 551 | item = item .. escape(matches[i].text:sub(start_of_last_match, pos - 1)) |
| 510 | end | 552 | end |
| 553 | |||
| 554 | start_of_last_match = pos | ||
| 511 | end | 555 | end |
| 512 | end | 556 | end |
| 513 | 557 | ||
| 514 | items[#items + 1] = style .. matches[i].text | 558 | if start_of_last_match then |
| 559 | local last_pos = matches[i].positions[#matches[i].positions] | ||
| 560 | |||
| 561 | if char_positions[start_of_last_match] and char_positions[last_pos + 1] then | ||
| 562 | item = item .. highlight .. | ||
| 563 | escape(matches[i].text:sub(start_of_last_match, last_pos)) .. | ||
| 564 | end_highlight .. | ||
| 565 | escape(matches[i].text:sub(last_pos + 1)) | ||
| 566 | else | ||
| 567 | item = item .. escape(matches[i].text:sub(start_of_last_match)) | ||
| 568 | end | ||
| 569 | end | ||
| 570 | |||
| 571 | items[#items + 1] = item | ||
| 515 | end | 572 | end |
| 516 | 573 | ||
| 517 | return items | 574 | return items |
| @@ -864,7 +921,11 @@ local function handle_edit() · 346 unmodified lines | |||
| 864 | if selectable_items then | 921 | if selectable_items then |
| 865 | matches = {} | 922 | matches = {} |
| 866 | for i, match in ipairs(fuzzy_find(line, selectable_items)) do | 923 | for i, match in ipairs(fuzzy_find(line, selectable_items)) do |
| 867 | matches[i] = { index = match, text = selectable_items[match] } | 924 | matches[i] = { |
| 925 | index = match[1], | ||
| 926 | text = selectable_items[match[1]], | ||
| 927 | positions = match[2], | ||
| 928 | } | ||
| 868 | end | 929 | end |
| 869 | 930 | ||
| 870 | if line == '' and default_item then | 931 | if line == '' and default_item then |
| @@ -1780,7 +1841,7 @@ complete = function () · 909 unmodified lines | |||
| 1780 | completion_pos = completion_pos or 1 | 1841 | completion_pos = completion_pos or 1 |
| 1781 | for i, match in ipairs(fuzzy_find(before_cur:sub(completion_pos), | 1842 | for i, match in ipairs(fuzzy_find(before_cur:sub(completion_pos), |
| 1782 | completions, opts.case_sensitive)) do | 1843 | completions, opts.case_sensitive)) do |
| 1783 | completion_buffer[i] = completions[match] | 1844 | completion_buffer[i] = completions[match[1]] |
| 1784 | end | 1845 | end |
| 1785 | 1846 | ||
| 1786 | render() | 1847 | render() |
| @@ -2100,7 +2161,7 @@ mp.register_script_message('complete', function(list, start_pos) · 313 unmodified lines | |||
| 2100 | completion_append = '' | 2161 | completion_append = '' |
| 2101 | for i, match in ipairs(fuzzy_find(line:sub(completion_pos, cursor), | 2162 | for i, match in ipairs(fuzzy_find(line:sub(completion_pos, cursor), |
| 2102 | completions)) do | 2163 | completions)) do |
| 2103 | completion_buffer[i] = completions[match] | 2164 | completion_buffer[i] = completions[match[1]] |
| 2104 | end | 2165 | end |
| 2105 | 2166 | ||
| 2106 | render() | 2167 | render() |