tmux-fingers/src/fingers/hinter.cr

154 lines
3.6 KiB
Crystal

require "../huffman"
require "./config"
require "./match_formatter"
require "./types"
module Fingers
class Hinter
@formatter : Formatter
@patterns : Array(String)
@alphabet : Array(String)
@pattern : Regex | Nil
@hints : Array(String) | Nil
@n_matches : Int32 | Nil
def initialize(
input : Array(String),
width : Int32,
state : Fingers::State,
output : Printer,
patterns = Fingers.config.patterns,
alphabet = Fingers.config.alphabet,
huffman = Huffman.new,
formatter = ::Fingers::MatchFormatter.new
)
@lines = input
@width = width
@hints_by_text = {} of String => String
@lookup_table = {} of String => String
@state = state
@output = output
@formatter = formatter
@huffman = huffman
@patterns = patterns
@alphabet = alphabet
end
def run
lines[0..-2].each { |line| process_line(line, "\n") }
process_line(lines[-1], "")
# STDOUT.flush
output.flush
build_lookup_table!
end
def lookup(hint)
lookup_table.fetch(hint) { nil }
end
def matches
@matches ||= @hints_by_text.keys.uniq!.flatten
end
# private
private getter :hints,
:hints_by_text,
:input,
:lookup_table,
:width,
:state,
:formatter,
:huffman,
:output,
:patterns,
:alphabet
def build_lookup_table!
@lookup_table = hints_by_text.invert
end
def process_line(line, ending)
result = line.gsub(pattern) { |_m| replace($~) }
result = Fingers.config.backdrop_style + result
double_width_correction = ((line.bytesize - line.size) / 3).round.to_i
padding_amount = (width - line.size - double_width_correction)
padding = padding_amount > 0 ? " " * padding_amount : ""
output.print(result + padding + ending)
end
def pattern : Regex
@pattern ||= Regex.new("(#{patterns.join('|')})")
end
def hints : Array(String)
return @hints.as(Array(String)) if !@hints.nil?
@hints = huffman.generate_hints(alphabet: alphabet, n: n_matches)
end
def replace(match)
text = match[0]
captured_text = match["match"]? || text
if match["match"]?
match_start, match_end = {match.begin(0), match.end(0)}
capture_start, capture_end = find_capture_offset(match).not_nil!
capture_offset = {capture_start - match_start, captured_text.size}
else
capture_offset = nil
end
if hints_by_text.has_key?(captured_text)
hint = hints_by_text[captured_text]
else
hint = hints.pop
raise "Too many matches" if hint.nil?
hints_by_text[captured_text] = hint
end
formatter.format(
hint: hint,
highlight: text,
selected: state.selected_hints.includes?(hint),
offset: capture_offset
)
end
def find_capture_offset(match : Regex::MatchData) : Tuple(Int32, Int32) | Nil
index = capture_indices.find { |i| match[i]? }
return nil unless index
{match.begin(index), match.end(index)}
end
getter capture_indices : Array(Int32) do
pattern.name_table.compact_map { |k, v| v == "match" ? k : nil }
end
def n_matches : Int32
return @n_matches.as(Int32) if !@n_matches.nil?
match_set = Set(String).new
lines.each do |line|
line.scan(pattern) do |match|
match_set.add(match[0]?.not_nil!)
end
end
@n_matches = match_set.size
match_set.size
end
private property lines : Array(String)
end
end