brintos

brintos / llvm-project-archived public Read only

0
0
Text · 4.6 KiB · d70789c Raw
144 lines · python
1import os2import re3import sys4import subprocess5import tempfile6 7import lldb8 9 10@lldb.command()11def fzf_history(debugger, cmdstr, ctx, result, _):12    """Use fzf to search and select from lldb command history."""13    history_file = os.path.expanduser("~/.lldb/lldb-widehistory")14    if not os.path.exists(history_file):15        result.SetError("history file does not exist")16        return17    history = _load_history(debugger, history_file)18 19    if sys.platform != "darwin":20        # The ability to integrate fzf's result into lldb uses copy and paste.21        # In absense of copy and paste, run the selected command directly.22        temp_file = tempfile.NamedTemporaryFile("r")23        fzf_command = (24            "fzf",25            "--no-sort",26            f"--query={cmdstr}",27            f"--bind=enter:execute-silent(echo -n {{}} > {temp_file.name})+accept",28        )29        subprocess.run(fzf_command, input=history, text=True)30        command = temp_file.read()31        debugger.HandleCommand(command)32        return33 34    # Capture the current pasteboard contents to restore after overwriting.35    paste_snapshot = subprocess.run("pbpaste", text=True, capture_output=True).stdout36 37    # On enter, copy the selected history entry into the pasteboard.38    fzf_command = (39        "fzf",40        "--no-sort",41        f"--query={cmdstr}",42        "--bind=enter:execute-silent(echo -n {} | pbcopy)+close",43    )44    completed = subprocess.run(fzf_command, input=history, text=True)45    # 130 is used for CTRL-C or ESC.46    if completed.returncode not in (0, 130):47        result.SetError("fzf failed")48        return49 50    # Get the user's selected history entry.51    selected_command = subprocess.run("pbpaste", text=True, capture_output=True).stdout52    if selected_command == paste_snapshot:53        # Nothing was selected, no cleanup needed.54        return55 56    _handle_command(debugger, selected_command)57 58    # Restore the pasteboard's contents.59    subprocess.run("pbcopy", input=paste_snapshot, text=True)60 61 62def _handle_command(debugger, command):63    """Try pasting the command, and failing that, run it directly."""64    if not command:65        return66 67    # Use applescript to paste the selected result into lldb's console.68    paste_command = (69        "osascript",70        "-e",71        'tell application "System Events" to keystroke "v" using command down',72    )73    completed = subprocess.run(paste_command, capture_output=True)74 75    if completed.returncode != 0:76        # The above applescript requires the "control your computer" permission.77        #     Settings > Private & Security > Accessibility78        # If not enabled, fallback to running the command.79        debugger.HandleCommand(command)80 81 82# `session history` example formatting:83#    1: first command84#    2: penultimate command85#    3: latest command86_HISTORY_PREFIX = re.compile(r"^\s+\d+:\s+")87 88 89def _load_session_history(debugger):90    """Load and parse lldb session history."""91    result = lldb.SBCommandReturnObject()92    interp = debugger.GetCommandInterpreter()93    interp.HandleCommand("session history", result)94    history = result.GetOutput()95    commands = []96    for line in history.splitlines():97        # Strip the prefix.98        command = _HISTORY_PREFIX.sub("", line)99        commands.append(command)100    return commands101 102 103def _load_persisted_history(history_file):104    """Load and decode lldb persisted history."""105    with open(history_file) as f:106        history_contents = f.read()107 108    # Some characters (ex spaces and newlines) are encoded as octal values, but109    # as _characters_ (not bytes). Space is the string r"\\040".110    history_decoded = re.sub(r"\\0([0-7][0-7])", _decode_char, history_contents)111    history_lines = history_decoded.splitlines()112 113    # Skip the header line (_HiStOrY_V2_)114    del history_lines[0]115    return history_lines116 117 118def _load_history(debugger, history_file):119    """Load, decode, parse, and prepare lldb history for fzf."""120    # Persisted history is older (earlier).121    history_lines = _load_persisted_history(history_file)122    # Session history is newer (later).123    history_lines.extend(_load_session_history(debugger))124 125    # Reverse to show latest first.126    history_lines.reverse()127 128    history_commands = []129    history_seen = set()130    for line in history_lines:131        line = line.strip()132        # Skip empty lines, single character commands, and duplicates.133        if line and len(line) > 1 and line not in history_seen:134            history_commands.append(line)135            history_seen.add(line)136 137    return "\n".join(history_commands)138 139 140def _decode_char(match):141    """Decode octal strings ('\0NN') into a single character string."""142    code = int(match.group(1), base=8)143    return chr(code)144