Module: FzfRuby
- Defined in:
- lib/fzf/version.rb,
lib/fzf.rb,
lib/fzf/tasks.rb,
lib/fzf/platform.rb,
ext/fzf/extconf.rb
Overview
Gem version constant
Defined Under Namespace
Modules: Installer, Platform, Tasks Classes: FzfNotFoundError
Constant Summary collapse
- VENDORED_BINARY =
Binary installed with the gem itself by ext/fzf/extconf.rb.
File.join(__dir__, "fzf", "bin", "fzf").freeze
- OPTION_FLAGS =
Maps Ruby keyword options to fzf CLI flags. Organized by category, mirroring the sections of
fzf --help(v0.73).Every flag fzf accepts should have exactly one entry here — this hash is the single source of truth for the supported option surface. Adding a flag means adding a line here, plus listing it in BOOLEAN_OPTIONS or OPTIONAL_VALUE_OPTIONS if it is not a plain required-value flag.
{ # --- Selection --- multi: "--multi", accept_nth: "--accept-nth", # fields to print on accept id_nth: "--id-nth", # item identity fields for reloads # --- Search behavior --- query: "--query", # prefill the query string filter: "--filter", # non-interactive filter mode delimiter: "--delimiter", nth: "--nth", # fields to match against with_nth: "--with-nth", # fields to display scheme: "--scheme", # default | path | history tiebreak: "--tiebreak", # length | chunk | begin | end | index tail: "--tail", # max items to keep in memory exact: "--exact", literal: "--literal", # do not normalize latin letters disabled: "--disabled", # do not perform search ignore_case: "--ignore-case", no_ignore_case: "--no-ignore-case", # force case-sensitive smart_case: "--smart-case", # fzf's default no_extended: "--no-extended", # disable extended-search syntax no_sort: "--no-sort", tac: "--tac", # reverse input order track: "--track", # track selection across updates sync: "--sync", # for multi-staged filtering cycle: "--cycle", select_1: "--select-1", # auto-select if only one match exit_0: "--exit-0", # exit immediately if no match # --- Prompt / input line --- prompt: "--prompt", pointer: "--pointer", marker: "--marker", marker_multi_line: "--marker-multi-line", ghost: "--ghost", # placeholder shown on empty input no_input: "--no-input", # hide the input section input_border: "--input-border", input_label: "--input-label", input_label_pos: "--input-label-pos", # --- Info line --- info: "--info", # default | inline | hidden | inline-right info_command: "--info-command", separator: "--separator", no_separator: "--no-separator", # --- Header --- header: "--header", header_lines: "--header-lines", # treat first N input lines as header header_first: "--header-first", # print header above the prompt header_border: "--header-border", header_lines_border: "--header-lines-border", header_label: "--header-label", header_label_pos: "--header-label-pos", # --- Footer --- footer: "--footer", footer_border: "--footer-border", footer_label: "--footer-label", footer_label_pos: "--footer-label-pos", # --- Preview --- preview: "--preview", preview_window: "--preview-window", preview_border: "--preview-border", preview_label: "--preview-label", preview_label_pos: "--preview-label-pos", preview_wrap_sign: "--preview-wrap-sign", # --- Layout / appearance --- height: "--height", min_height: "--min-height", # floor when height is a percentage layout: "--layout", # default | reverse | reverse-list style: "--style", # default | minimal | full[:BORDER] border: "--border", # rounded | sharp | bold | double | none | ... border_label: "--border-label", border_label_pos: "--border-label-pos", list_border: "--list-border", list_label: "--list-label", list_label_pos: "--list-label-pos", margin: "--margin", # TRBL | TB,RL | T,RL,B | T,R,B,L padding: "--padding", # same forms as margin color: "--color", no_color: "--no-color", no_bold: "--no-bold", ansi: "--ansi", # process ANSI color codes in input # --- List rendering --- highlight_line: "--highlight-line", wrap: "--wrap", # char | word wrap_sign: "--wrap-sign", no_multi_line: "--no-multi-line", # only meaningful with --read0 gap: "--gap", # blank lines between items gap_line: "--gap-line", # string drawn in each gap keep_right: "--keep-right", no_hscroll: "--no-hscroll", hscroll_off: "--hscroll-off", scroll_off: "--scroll-off", ellipsis: "--ellipsis", tabstop: "--tabstop", scrollbar: "--scrollbar", no_scrollbar: "--no-scrollbar", raw: "--raw", # show non-matching items gutter: "--gutter", gutter_raw: "--gutter-raw", freeze_left: "--freeze-left", # fields frozen on the left freeze_right: "--freeze-right", # --- History --- history: "--history", # file path, not shell history history_size: "--history-size", # --- Keybinds / navigation (bind gets special handling in build_command) --- bind: "--bind", jump_labels: "--jump-labels", filepath_word: "--filepath-word", # word movements respect path separators # --- Process / integration --- with_shell: "--with-shell", # shell used for child processes tmux: "--tmux", # alias of --popup popup: "--popup", # requires tmux 3.3+ / Zellij 0.44+ listen: "--listen", # HTTP server for remote actions }.freeze
- BOOLEAN_OPTIONS =
Bare flags that take no value — emitted only when the option is truthy.
%i[ multi exact literal disabled ignore_case no_ignore_case smart_case no_extended no_sort tac track sync cycle select_1 exit_0 ansi no_input no_separator header_first no_color no_bold highlight_line no_multi_line keep_right no_hscroll no_scrollbar raw filepath_word ].freeze
- OPTIONAL_VALUE_OPTIONS =
Flags whose value is optional (
--flagor--flag=VALUEin fzf's help). Passingtrueemits the bare flag; any other value emits--flag=VALUE. %i[ border preview_border list_border input_border header_border header_lines_border footer_border wrap gap gap_line scrollbar tmux popup listen ].freeze
- LIST_RESULT_OPTIONS =
Options that put fzf in a mode where it can emit more than one line: multi-select accepts several items, and filter mode prints every match. Any of these makes fzf() return an Array instead of a single String.
%i[multi filter].freeze
- VERSION =
"0.1.1"
Class Method Summary collapse
-
.build_command(options) ⇒ Array<String>
Build the fzf command as an array (avoids shell interpolation).
-
.fzf(*menu_items, **options) ⇒ String, ...
Invoke fzf with the given menu items and options.
-
.fzf_binary ⇒ Object
Resolve the fzf binary path.
-
.list_result?(options) ⇒ Boolean
Does this option set put fzf in a mode that can return more than one line?.
-
.project_root ⇒ Object
Find the project root — the directory containing the Gemfile.
-
.run_fzf(cmd, items) ⇒ Array<String>?
Pipe items into fzf via stdin and capture the selected lines.
-
.serialize_bind(value) ⇒ String
Serialize bind option to fzf's comma-separated key:action format.
-
.serialize_value(value) ⇒ String
Serialize an option value for the command line.
Class Method Details
.build_command(options) ⇒ Array<String>
Build the fzf command as an array (avoids shell interpolation).
Values are always attached with = (--prompt=Pick> ) rather than as a
separate argument. fzf accepts both forms, but the = form is unambiguous
for values that begin with a dash (e.g. pointer: "->") and is required for
distinguishing a bare optional-value flag from one carrying a value.
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/fzf.rb', line 282 def build_command() cmd = [FzfRuby.fzf_binary] .each do |key, value| flag = OPTION_FLAGS[key] next unless flag # ignore unknown options silently if BOOLEAN_OPTIONS.include?(key) # Boolean flags are added only when truthy cmd << flag if value elsif value.nil? || value == false # Any option explicitly disabled is simply omitted next elsif OPTIONAL_VALUE_OPTIONS.include?(key) # `true` means "use fzf's default" — emit the flag with no value cmd << (value == true ? flag : "#{flag}=#{serialize_value(value)}") elsif key == :bind # Keybinds accept a string or a hash of { key_combo => action } cmd << "#{flag}=#{serialize_bind(value)}" else cmd << "#{flag}=#{serialize_value(value)}" end end cmd end |
.fzf(*menu_items, **options) ⇒ String, ...
Invoke fzf with the given menu items and options.
252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/fzf.rb', line 252 def fzf(*, **) # Flatten in case caller passes an array instead of splatted args items = .flatten.map(&:to_s) cmd = build_command() result = run_fzf(cmd, items) # Return nil on cancel, an array for the modes that can yield several # lines, a single string otherwise return nil if result.nil? list_result?() ? result : result.first end |
.fzf_binary ⇒ Object
Resolve the fzf binary path. Priority: FZF_PATH env var → project bin/fzf → gem's own bin → system PATH
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/fzf.rb', line 42 def self.fzf_binary # Explicit override via environment variable from_env = ENV["FZF_PATH"] return from_env if from_env && File.executable?(from_env) # Local project binary (installed via `rake fzf:install`) local = File.join(project_root, "bin", "fzf") return local if File.executable?(local) # Binary downloaded when the gem was installed return VENDORED_BINARY if File.executable?(VENDORED_BINARY) # Fall back to system PATH "fzf" end |
.list_result?(options) ⇒ Boolean
Does this option set put fzf in a mode that can return more than one line?
269 270 271 |
# File 'lib/fzf.rb', line 269 def list_result?() LIST_RESULT_OPTIONS.any? { |key| [key] } end |
.project_root ⇒ Object
Find the project root — the directory containing the Gemfile. Falls back to the current working directory.
60 61 62 63 64 65 66 |
# File 'lib/fzf.rb', line 60 def self.project_root if defined?(Bundler) Bundler.root.to_s else Dir.pwd end end |
.run_fzf(cmd, items) ⇒ Array<String>?
Pipe items into fzf via stdin and capture the selected lines.
fzf reads candidates from stdin and opens /dev/tty for interactive user input, so IO.popen works without additional TTY wiring.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'lib/fzf.rb', line 346 def run_fzf(cmd, items) input = items.join("\n") output = IO.popen(cmd, "r+") do |io| io.write(input) io.close_write io.read end # fzf exit codes: 0 = ok, 1 = no match, 2 = error, 130 = cancelled return nil unless $?.success? selections = output.split("\n").reject(&:empty?) selections.empty? ? nil : selections rescue Errno::ENOENT raise FzfNotFoundError, "fzf is not installed or not found in PATH. Install it: https://github.com/junegunn/fzf#installation" end |
.serialize_bind(value) ⇒ String
Serialize bind option to fzf's comma-separated key:action format.
327 328 329 330 331 332 333 334 335 336 |
# File 'lib/fzf.rb', line 327 def serialize_bind(value) case value when Hash # Convert { "ctrl-a" => "select-all", "ctrl-d" => "deselect-all" } # into "ctrl-a:select-all,ctrl-d:deselect-all" value.map { |k, v| "#{k}:#{v}" }.join(",") else value.to_s end end |
.serialize_value(value) ⇒ String
Serialize an option value for the command line.
Symbols are treated as Ruby-flavoured spellings of fzf's hyphenated enum values (layout: :reverse_list → "reverse-list"). Every other type is passed through verbatim — strings are user data (labels, paths, preview commands) and must never be rewritten.
318 319 320 |
# File 'lib/fzf.rb', line 318 def serialize_value(value) value.is_a?(Symbol) ? value.to_s.tr("_", "-") : value.to_s end |