-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lldb] Add fzf_history command to examples #128571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[lldb] Add fzf_history command to examples #128571
Conversation
@llvm/pr-subscribers-lldb Author: Dave Lee (kastiglione) ChangesFull diff: https://github.com/llvm/llvm-project/pull/128571.diff 1 Files Affected:
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
+
+
+@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)
|
✅ With the latest revision this PR passed the Python code formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using pbcopy/pbpaste
, which makes this macOS only, could you use a Python temp file and read/write from that?
lldb/examples/python/fzf_history.py
Outdated
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that fzf_history here is using pbcopy/pbpaste
and applescript
here for the clipboard operations but is it possible to just say that "clipboard operations are only supported on macOS" and still run the script?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JDevlieghere I didn't do that because it would be an implementation I wouldn't even want to use, for platforms I don't use. Without a way to do copy and paste, the command could skip writing to write to a temp file, since fzf's default behavior is to print the selected line to stdout. |
I added a featureless invocation of fzf for when copy and paste support isn't available. |
What I have in mind seems significantly simpler than what you currently have, wouldn't require special permissions and would make this work on other POSIX platforms (but admittedly not Windows). I'm not sure I understand the pushback. Maybe I'm missing something? Or what I'm suggesting isn't possible? What I had in mind was to pipe the output to a temporary file like this:
and then instead of doing the dance with osascript, you just open that file and read its contents. |
@JDevlieghere and then… run the command directly? When I read your first comment, I think I must have not realized the implication (because see below).
I want it to work as close to the same way as it works with fzf's shell integration. I don't want the selected command to run automatically, and I don't want to have to manually copy and paste the command. The only reason I went forward with this is because I saw an approach that would achieve those. I also thought someone else might be motivated to figure out how to achieve those on other platforms. |
@JDevlieghere the latest commit makes fzf write to a temp file, and then those contents are run as a command (for non-Darwin platforms). For Darwin, the pasting is still used to achieve parity with fzf history search in shells. |
A thought, would it make sense to add a function (to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I missed the part where it pastes the command but doesn't actually run it. Setting the string on the command line would be a very IOHandler specific thing and that's not something we expose. Sure, you could add it to the CommandInterpeter
but that's definitely above and beyond for an example. I think this looks good!
Adds a
fzf_history
to the examples directory.This python command invokes fzf to select from lldb's command history.
Tighter integration is available on macOS, via commands for copy and paste. The user's chosen history entry back is pasted into the lldb console (via AppleScript). By pasting it, users have the opportunity to edit it before running it. This matches how fzf's history search works.
Without copy and paste, the user's chosen history entry is printed to screen and then run automatically.