import bisect import re from collections import defaultdict from typing import Optional, Tuple IDENTIFIER_RE = re.compile(r"(? Tuple[Optional[str], str]: lines = template_str.splitlines() identifier_contexts = defaultdict(list) for i, line in enumerate(lines): for match in re.finditer(IDENTIFIER_RE, line): name = match[1] or match[2] for j in range(max(0, i - 2), min(len(lines) - 1, i + 2)): ctx = identifier_contexts[name] idx = bisect.bisect_left(ctx, j) if idx >= len(ctx) or ctx[idx] != j: ctx.insert(idx, j) replacements = {} for identifier, ctx in identifier_contexts.items(): def colorize(match): name = match[1] or match[2] if name == identifier: return f"\x1b[31m%{match[1] or match[2]}\x1b[0m" else: return match[0] prev = 0 printed = 0 for line_idx in ctx: print(re.sub(IDENTIFIER_RE, colorize, lines[line_idx])) printed += 1 if prev and line_idx - prev > 1: print("\x1b[1m---\x1b[0m") printed += 1 prev = line_idx replacements[identifier] = input("\x1b[1m => " + identifier + ":\x1b[0m ") printed += 1 print(f"\r\x1b[{printed}F\x1b[0J", end="") def replace(match): return replacements[match[1] or match[2]] return replacements.get("name"), re.sub(IDENTIFIER_RE, replace, template_str).replace("%%","%")