vim9script import autoload "kg8m/plugin.vim" import autoload "kg8m/plugin/fzf.vim" import autoload "kg8m/util/list.vim" as listUtil import autoload "kg8m/util/string.vim" as stringUtil final cache = {} # Respect `$RIPGREP_EXTRA_OPTIONS` (fzf's `:Rg` doesn't respect it) command! -nargs=+ -complete=customlist,Complete FzfGrep Run() export def EnterCommand(preset: string = "", options = {}): void const escaped_preset = escape(preset, ".?*+^$()[]{}\\") ->substitute('^-', '\\-', "") ->substitute("'", "'\\\\''", "g") const preset_prefix = get(options, "word_boundary", false) && preset =~# '^\w' ? '\b' : "" const preset_suffix = get(options, "word_boundary", false) && preset =~# '\w$' ? '\b' : "" const hint =<< trim HINT Hint: - :FzfGrep '{PATTERN}' # Search the current directory - :FzfGrep '{PATTERN}' {PATH_TO_SEARCH} # Search specified files/directories - :FzfGrep '{PATTERN}' -g !{PATH_TO_EXCLUDE} # Exclude files/directories for sarching (--glob) - :FzfGrep '{PATTERN}' -s # Search case sensitively (--case-sensitive) - :FzfGrep '{PATTERN}' -o # Print only the matched parts of a matching line (--only-matching) - :FzfGrep '{PATTERN}' -v # Invert matching (--invert-match) - :FzfGrep '{PATTERN}' -E {ENCODING} # Specify the text encoding, e.g., shift-jis (--encoding) HINT echo hint->join("\n") .. "\n\n" feedkeys($":\FzfGrep\'{preset_prefix}{escaped_preset}{preset_suffix}'\", "t") enddef def Run(args: string): void const grep_command = ["rg", GrepFullOptions(), args]->join(" ") const fzf_options = [ "--header", JoinPresences(["Grep:", GrepExplicitOptions(), args]), "--delimiter", ":", "--preview", "preview {}", "--preview-window", "down:75%:wrap:nohidden:+{2}-/2", ] fzf#vim#grep(grep_command, { options: fzf_options }) enddef def Complete(arglead: string, _cmdline: string, _curpos: number): list if empty(arglead) || stringUtil.StartsWith(arglead, "-") if !has_key(cache, "grep_option_candidates") cache.grep_option_candidates = system(CommandToShowGrepOptionCandidates())->split("\n") endif const pattern = $"^{arglead}" return cache.grep_option_candidates->copy()->filter((_, item) => item =~# pattern) elseif stringUtil.StartsWith(arglead, "$") const pattern = arglead[1 : -1] const envvar_names = getcompletion(pattern, "environment") if empty(envvar_names) return getcompletion(arglead, "file") else const prefix = "$" return envvar_names->mapnew((_, item) => $"{prefix}{item}") endif else var pattern = arglead if pattern =~# '^["'']' pattern = pattern[1 :] endif if pattern =~# '^\' pattern = pattern[1 :] endif if pattern =~# '^!' pattern = pattern[1 :] endif const prefix = strpart(arglead, 0, len(arglead) - len(pattern)) return getcompletion(pattern, "file")->map((_, item) => $"{prefix}{item}") endif enddef def GrepFullOptions(): string return JoinPresences([GrepImplicitOptions(), GrepExplicitOptions()]) enddef def GrepImplicitOptions(): string return "--column --line-number --no-heading --with-filename --color always" enddef def GrepExplicitOptions(): string if has_key(cache, "grep_explicit_options") return cache.grep_explicit_options endif if empty($RIPGREP_EXTRA_OPTIONS) cache.grep_explicit_options = "" else final splitted = split($RIPGREP_EXTRA_OPTIONS, " ") const escaped = map(splitted, (_, option) => shellescape(option)) cache.grep_explicit_options = join(escaped, " ") endif return cache.grep_explicit_options enddef def CommandToShowGrepOptionCandidates(): string const show_help = "rg --help" const filter_option_lines = "grep -E '^\\s*-'" const extract_options = "grep -E '\\-[.0-9a-zA-Z]\\b|--[-0-9a-zA-Z]+' -o" const sort_and_uniquify = "sort -u" return [show_help, filter_option_lines, extract_options, sort_and_uniquify]->join(" | ") enddef def JoinPresences(list: list): string const Mapper = (item) => empty(item) ? false : item return list->listUtil.FilterMap(Mapper)->join(" ") enddef plugin.EnsureSourced("fzf.vim")