[Lldb-commits] [lldb] [lldb] Add fzf_history command to examples (PR #128571)

Dave Lee via lldb-commits lldb-commits at lists.llvm.org
Mon Feb 24 14:41:26 PST 2025


https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/128571

>From 57638e91c97486ad7739f9658fe88f7fb4d61fb2 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Mon, 24 Feb 2025 12:48:17 -0800
Subject: [PATCH 1/5] [lldb] Add fzf_history command to examples

---
 lldb/examples/python/fzf_history.py | 99 +++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)
 create mode 100644 lldb/examples/python/fzf_history.py

diff --git a/lldb/examples/python/fzf_history.py b/lldb/examples/python/fzf_history.py
new file mode 100644
index 0000000000000..4647a3532b0df
--- /dev/null
+++ b/lldb/examples/python/fzf_history.py
@@ -0,0 +1,99 @@
+import os
+import re
+import sys
+import subprocess
+
+import lldb
+
+
+ at lldb.command()
+def fzf_history(debugger, cmdstr, ctx, result, _):
+    if sys.platform != 'darwin':
+        result.SetError("fzf_history supports macOS only")
+        return
+
+    # Capture the current pasteboard contents to restore after overwriting.
+    paste_snapshot = subprocess.run("pbpaste", text=True, capture_output=True).stdout
+
+    # On enter, copy the selected history entry into the pasteboard.
+    fzf_command = (
+        "fzf",
+        "--no-sort",
+        f"--query={cmdstr}",
+        "--bind=enter:execute-silent(echo -n {} | pbcopy)+close",
+    )
+
+    history_file = os.path.expanduser("~/.lldb/lldb-widehistory")
+    if not os.path.exists(history_file):
+        result.SetError("history file does not exist")
+        return
+
+    history_commands = _load_history(history_file)
+    fzf_input = "\n".join(history_commands)
+    completed = subprocess.run(fzf_command, input=fzf_input, text=True)
+    # 130 is used for CTRL-C or ESC.
+    if completed.returncode not in (0, 130):
+        result.SetError(f"fzf failed: {completed.stderr}")
+        return
+
+    # Get the user's selected history entry.
+    selected_command = subprocess.run("pbpaste", text=True, capture_output=True).stdout
+    if selected_command == paste_snapshot:
+        # Nothing was selected, no cleanup needed.
+        return
+
+    _handle_command(debugger, selected_command)
+
+    # Restore the pasteboard's contents.
+    subprocess.run("pbcopy", input=paste_snapshot, text=True)
+
+
+def _handle_command(debugger, command):
+    """Try pasting the command, and failing that, run it directly."""
+    if not command:
+        return
+
+    # Use applescript to paste the selected result into lldb's console.
+    paste_command = (
+        "osascript",
+        "-e",
+        'tell application "System Events" to keystroke "v" using command down',
+    )
+    completed = subprocess.run(paste_command, capture_output=True)
+
+    if completed.returncode != 0:
+        # The above applescript requires the "control your computer" permission.
+        #     Settings > Private & Security > Accessibility
+        # If not enabled, fallback to running the command.
+        debugger.HandleCommand(command)
+
+
+def _load_history(history_file):
+    """Load, decode, and parse an lldb history file."""
+    with open(history_file) as f:
+        history_contents = f.read()
+
+    history_decoded = re.sub(r"\\0([0-7][0-7])", _decode_char, history_contents)
+    history_lines = history_decoded.splitlines()
+
+    # Skip the header line (_HiStOrY_V2_)
+    del history_lines[0]
+    # Reverse to show latest first.
+    history_lines.reverse()
+
+    history_commands = []
+    history_seen = set()
+    for line in history_lines:
+        line = line.strip()
+        # Skip empty lines, single character commands, and duplicates.
+        if line and len(line) > 1 and line not in history_seen:
+            history_commands.append(line)
+            history_seen.add(line)
+
+    return history_commands
+
+
+def _decode_char(match):
+    """Decode octal strings ('\0NN') into a single character string."""
+    code = int(match.group(1), base=8)
+    return chr(code)

>From d1106bc7165d8a62a1805ec09afdf62e5b7f09ff Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Mon, 24 Feb 2025 12:56:35 -0800
Subject: [PATCH 2/5] Fix code formatting

---
 lldb/examples/python/fzf_history.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/examples/python/fzf_history.py b/lldb/examples/python/fzf_history.py
index 4647a3532b0df..b683078465985 100644
--- a/lldb/examples/python/fzf_history.py
+++ b/lldb/examples/python/fzf_history.py
@@ -8,7 +8,7 @@
 
 @lldb.command()
 def fzf_history(debugger, cmdstr, ctx, result, _):
-    if sys.platform != 'darwin':
+    if sys.platform != "darwin":
         result.SetError("fzf_history supports macOS only")
         return
 

>From 7fb2bfc015e1550dde6f3d54e932e8bc6ccbe0f7 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Mon, 24 Feb 2025 12:59:08 -0800
Subject: [PATCH 3/5] Add docstring for fzf_history

---
 lldb/examples/python/fzf_history.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lldb/examples/python/fzf_history.py b/lldb/examples/python/fzf_history.py
index b683078465985..569abe54dc76f 100644
--- a/lldb/examples/python/fzf_history.py
+++ b/lldb/examples/python/fzf_history.py
@@ -8,6 +8,7 @@
 
 @lldb.command()
 def fzf_history(debugger, cmdstr, ctx, result, _):
+    """Use fzf to search and select from lldb command history."""
     if sys.platform != "darwin":
         result.SetError("fzf_history supports macOS only")
         return

>From eff33674255e41d18d8943e74a2a142261525fc0 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Mon, 24 Feb 2025 14:35:16 -0800
Subject: [PATCH 4/5] Run featureless fzf when no copy and paste is available

---
 lldb/examples/python/fzf_history.py | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/lldb/examples/python/fzf_history.py b/lldb/examples/python/fzf_history.py
index 569abe54dc76f..8dfd916dc5d16 100644
--- a/lldb/examples/python/fzf_history.py
+++ b/lldb/examples/python/fzf_history.py
@@ -9,8 +9,17 @@
 @lldb.command()
 def fzf_history(debugger, cmdstr, ctx, result, _):
     """Use fzf to search and select from lldb command history."""
+    history_file = os.path.expanduser("~/.lldb/lldb-widehistory")
+    if not os.path.exists(history_file):
+        result.SetError("history file does not exist")
+        return
+    history = _load_history(history_file)
+
     if sys.platform != "darwin":
-        result.SetError("fzf_history supports macOS only")
+        # The ability to integrate fzf's result into lldb uses copy and paste.
+        # In absense of copy and paste, do the basics and forgo features.
+        fzf_command = ("fzf", "--no-sort", f"--query={query}")
+        subprocess.run(fzf_command, input=history)
         return
 
     # Capture the current pasteboard contents to restore after overwriting.
@@ -23,18 +32,10 @@ def fzf_history(debugger, cmdstr, ctx, result, _):
         f"--query={cmdstr}",
         "--bind=enter:execute-silent(echo -n {} | pbcopy)+close",
     )
-
-    history_file = os.path.expanduser("~/.lldb/lldb-widehistory")
-    if not os.path.exists(history_file):
-        result.SetError("history file does not exist")
-        return
-
-    history_commands = _load_history(history_file)
-    fzf_input = "\n".join(history_commands)
-    completed = subprocess.run(fzf_command, input=fzf_input, text=True)
+    completed = subprocess.run(fzf_command, input=history, text=True)
     # 130 is used for CTRL-C or ESC.
     if completed.returncode not in (0, 130):
-        result.SetError(f"fzf failed: {completed.stderr}")
+        result.SetError("fzf failed")
         return
 
     # Get the user's selected history entry.
@@ -70,7 +71,7 @@ def _handle_command(debugger, command):
 
 
 def _load_history(history_file):
-    """Load, decode, and parse an lldb history file."""
+    """Load, decode, parse, and prepare an lldb history file for fzf."""
     with open(history_file) as f:
         history_contents = f.read()
 
@@ -91,7 +92,7 @@ def _load_history(history_file):
             history_commands.append(line)
             history_seen.add(line)
 
-    return history_commands
+    return "\n".join(history_commands)
 
 
 def _decode_char(match):

>From fcdf670623e89a6d6adff50a9b667cdb1d042fa1 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Mon, 24 Feb 2025 14:41:10 -0800
Subject: [PATCH 5/5] Fix basic fzf invocation

---
 lldb/examples/python/fzf_history.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/examples/python/fzf_history.py b/lldb/examples/python/fzf_history.py
index 8dfd916dc5d16..62dde6ecf7357 100644
--- a/lldb/examples/python/fzf_history.py
+++ b/lldb/examples/python/fzf_history.py
@@ -19,7 +19,7 @@ def fzf_history(debugger, cmdstr, ctx, result, _):
         # The ability to integrate fzf's result into lldb uses copy and paste.
         # In absense of copy and paste, do the basics and forgo features.
         fzf_command = ("fzf", "--no-sort", f"--query={query}")
-        subprocess.run(fzf_command, input=history)
+        subprocess.run(fzf_command, input=history, text=True)
         return
 
     # Capture the current pasteboard contents to restore after overwriting.



More information about the lldb-commits mailing list