console.lua: highlight matching parts of selectable items

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.
This commit is contained in:
Guido Cella
2025-02-24 15:56:20 +01:00
committed by Kacper Michajłow
parent a5700484be
commit 72bb227bf1
2 changed files with 78 additions and 12 deletions
+5
View File
@@ -218,6 +218,11 @@ Configurable Options
The background color of the selected item.
``match_color``
Default: ``#0088FF``
The color of characters that match the searched string.
``case_sensitive``
Default: no on Windows, yes on other platforms.
+73 -12
View File
@@ -42,6 +42,7 @@ local opts = {
scale_with_window = "auto",
selected_color = '#222222',
selected_back_color = '#FFFFFF',
match_color = '#0088FF',
case_sensitive = platform ~= 'windows' and true or false,
history_dedup = true,
persist_history = false,
@@ -77,6 +78,8 @@ local terminal_styles = {
selected_completion = '\027[7m',
default_item = '\027[1m',
disabled = '\027[38;5;8m',
matched_position = '\027[34m',
match_end = '\027[39m',
}
local repl_active = false
@@ -183,6 +186,18 @@ local function len_utf8(str)
return len
end
local function utf8_positions(str)
local pos = 1
local positions = {true}
while pos <= str:len() do
pos = next_utf8(str, pos)
positions[pos] = true
end
return positions
end
-- Functions to calculate the font width.
local width_length_ratio = 0.5
@@ -467,9 +482,7 @@ local function fuzzy_find(needle, haystacks, case_sensitive)
return i[1] < j[1]
end)
for i, value in ipairs(result) do
result[i] = value[1]
end
return result
end
@@ -480,6 +493,9 @@ local function get_matches_to_print(terminal)
local items = {}
local max_lines = calculate_max_lines()
local escape = terminal and function (str) return str end or ass_escape
local highlight = terminal and terminal_styles.matched_position or
'{\\1c&H' .. option_color_to_ass(opts.match_color) .. '}'
if selected_match < first_match_to_print then
first_match_to_print = selected_match
@@ -491,27 +507,68 @@ local function get_matches_to_print(terminal)
#matches)
for i = first_match_to_print, last_match_to_print do
local style = ''
local item = ''
local end_highlight = terminal and terminal_styles.match_end or '{\\1c}'
if terminal then
if matches[i].index == default_item then
style = terminal_styles.default_item
item = terminal_styles.default_item
end
if i == selected_match then
style = style .. terminal_styles.selected_completion
item = item .. terminal_styles.selected_completion
end
else
if i == selected_match then
if searching_history and
mp.get_property('osd-border-style') == 'outline-and-shadow' then
style = get_selected_ass()
item = get_selected_ass()
else
style = '{\\1c&H' .. option_color_to_ass(opts.selected_color) .. '&}'
item = '{\\1c&H' .. option_color_to_ass(opts.selected_color) .. '&}'
end
end_highlight = item
end
end
items[#items + 1] = style .. matches[i].text
local char_positions = utf8_positions(matches[i].text)
local start_of_last_match = matches[i].positions[1]
if not start_of_last_match then
item = item .. escape(matches[i].text)
elseif start_of_last_match > 1 then
item = item .. escape(matches[i].text:sub(1, start_of_last_match - 1))
end
for j, pos in ipairs(matches[i].positions) do
local last_pos = matches[i].positions[j - 1]
if last_pos and pos > last_pos + 1 then
if char_positions[start_of_last_match] and char_positions[last_pos + 1] then
item = item .. highlight ..
escape(matches[i].text:sub(start_of_last_match, last_pos)) ..
end_highlight ..
escape(matches[i].text:sub(last_pos + 1, pos - 1))
else
item = item .. escape(matches[i].text:sub(start_of_last_match, pos - 1))
end
start_of_last_match = pos
end
end
if start_of_last_match then
local last_pos = matches[i].positions[#matches[i].positions]
if char_positions[start_of_last_match] and char_positions[last_pos + 1] then
item = item .. highlight ..
escape(matches[i].text:sub(start_of_last_match, last_pos)) ..
end_highlight ..
escape(matches[i].text:sub(last_pos + 1))
else
item = item .. escape(matches[i].text:sub(start_of_last_match))
end
end
items[#items + 1] = item
end
return items
@@ -864,7 +921,11 @@ local function handle_edit()
if selectable_items then
matches = {}
for i, match in ipairs(fuzzy_find(line, selectable_items)) do
matches[i] = { index = match, text = selectable_items[match] }
matches[i] = {
index = match[1],
text = selectable_items[match[1]],
positions = match[2],
}
end
if line == '' and default_item then
@@ -1780,7 +1841,7 @@ complete = function ()
completion_pos = completion_pos or 1
for i, match in ipairs(fuzzy_find(before_cur:sub(completion_pos),
completions, opts.case_sensitive)) do
completion_buffer[i] = completions[match]
completion_buffer[i] = completions[match[1]]
end
render()
@@ -2100,7 +2161,7 @@ mp.register_script_message('complete', function(list, start_pos)
completion_append = ''
for i, match in ipairs(fuzzy_find(line:sub(completion_pos, cursor),
completions)) do
completion_buffer[i] = completions[match]
completion_buffer[i] = completions[match[1]]
end
render()