From 1cd834e211ff8050df1da397bc5a3d400b04b0a3 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 29 Oct 2020 17:38:25 -0400 Subject: [PATCH 1/4] Use fzf by default --- Project.toml | 4 +++- src/InteractiveCodeSearch.jl | 35 +++++++++++------------------------ 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/Project.toml b/Project.toml index 2eca3ca..c85223e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,15 @@ name = "InteractiveCodeSearch" uuid = "54eb57ff-b09b-5185-b2f4-7a93509e494f" -version = "0.3.3-DEV" +version = "0.4.0-DEV" [deps] InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +fzf_jll = "214eeab7-80f7-51ab-84ad-2988db7cef09" [compat] +fzf_jll = "0.24" julia = "= 0.7, 1.0" [extras] diff --git a/src/InteractiveCodeSearch.jl b/src/InteractiveCodeSearch.jl index 72fc063..2f94d2e 100644 --- a/src/InteractiveCodeSearch.jl +++ b/src/InteractiveCodeSearch.jl @@ -43,6 +43,7 @@ module InteractiveCodeSearch export @search, @searchmethods import Pkg +import fzf_jll using Base using Base: IOError using InteractiveUtils: edit, gen_call_with_extracted_types, methodswith @@ -249,7 +250,8 @@ Configuration interface for `InteractiveCodeSearch`. ```julia using InteractiveCodeSearch -InteractiveCodeSearch.CONFIG.interactive_matcher = `peco` # default in terminal +InteractiveCodeSearch.CONFIG.interactive_matcher = `fzf ...` # default in terminal +InteractiveCodeSearch.CONFIG.interactive_matcher = `peco` InteractiveCodeSearch.CONFIG.interactive_matcher = `percol` InteractiveCodeSearch.CONFIG.interactive_matcher = `rofi -dmenu -i -p "🔎"` # use GUI matcher (default in non-terminal @@ -437,11 +439,6 @@ macro searchmethods(x) end end -const preferred_terminal = Cmd[ - `peco`, - `percol`, -] - const preferred_gui = Cmd[ `rofi -dmenu -i -p "🔎"`, # what else? @@ -469,24 +466,20 @@ function choose_preferred_command(f, commands::Vector{Cmd}) end end -# Julia 0.6 -const _preferred_terminal = preferred_terminal -const _preferred_gui = preferred_gui - function choose_interactive_matcher(; - preferred_terminal = _preferred_terminal, - preferred_gui = _preferred_gui, + preferred_gui = preferred_gui, gui = need_gui()) if gui return choose_preferred_command(preferred_gui) do return preferred_gui[1] end else - return choose_preferred_command(preferred_terminal) do - return choose_preferred_command(preferred_gui) do - return preferred_terminal[1] - end + previewer = "echo {} | sed 's/⏎/\\n/g'" + if Sys.which("pygmentize") !== nothing + previewer = "$previewer | pygmentize -l jl" end + fzf = fzf_jll.fzf() + return `$fzf --preview $previewer` end end @@ -496,17 +489,12 @@ function matcher_installation_tips(program::AbstractString) See https://github.com/peco/peco for how to install peco. """ elseif program == "rofi" - msg = """ + return """ See https://github.com/DaveDavenport/rofi for how to install rofi. """ else - msg = "" + return "" end - return """ - $msg - For terminal usage, `peco` is recommended. - See https://github.com/peco/peco for how to install peco. - """ end function maybe_warn_matcher(cmd = CONFIG.interactive_matcher) @@ -520,7 +508,6 @@ end function __init__() CONFIG.interactive_matcher = choose_interactive_matcher() - maybe_warn_matcher() end include("taskmanager.jl") From 2cd8bc4e2e7ab0d9178751db13c2c3246e5a6be8 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 29 Oct 2020 19:57:55 -0400 Subject: [PATCH 2/4] Support previewing file content --- Project.toml | 2 ++ src/InteractiveCodeSearch.jl | 25 +++++++++++++++--- src/preview.jl | 51 ++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/preview.jl diff --git a/Project.toml b/Project.toml index c85223e..a750a7a 100644 --- a/Project.toml +++ b/Project.toml @@ -3,12 +3,14 @@ uuid = "54eb57ff-b09b-5185-b2f4-7a93509e494f" version = "0.4.0-DEV" [deps] +Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" fzf_jll = "214eeab7-80f7-51ab-84ad-2988db7cef09" [compat] +Compat = "3.16" fzf_jll = "0.24" julia = "= 0.7, 1.0" diff --git a/src/InteractiveCodeSearch.jl b/src/InteractiveCodeSearch.jl index 2f94d2e..0d19074 100644 --- a/src/InteractiveCodeSearch.jl +++ b/src/InteractiveCodeSearch.jl @@ -46,6 +46,7 @@ import Pkg import fzf_jll using Base using Base: IOError +using Compat: addenv using InteractiveUtils: edit, gen_call_with_extracted_types, methodswith function _readandwrite(cmds) @@ -474,12 +475,28 @@ function choose_interactive_matcher(; return preferred_gui[1] end else - previewer = "echo {} | sed 's/⏎/\\n/g'" + preview_jl = joinpath(@__DIR__, "preview.jl") + preview_cmd = ` + $(Base.julia_cmd()) + --startup-file=no + --color=yes + --compile=min + -O0 + $preview_jl + ` + previewer = string(preview_cmd) + if startswith(previewer, '`') && endswith(previewer, '`') + previewer = previewer[2:end-1] + end + cmd = fzf_jll.fzf() + if !occursin("--layout", get(ENV, "FZF_DEFAULT_OPTS", "")) + cmd = `$cmd --layout=reverse` + end + cmd = `$cmd --preview $(previewer * " {}")` if Sys.which("pygmentize") !== nothing - previewer = "$previewer | pygmentize -l jl" + cmd = addenv(cmd, "_INTERACTIVECODESEARCH_JL_HIGHLIGHTER" => "pygmentize -l jl") end - fzf = fzf_jll.fzf() - return `$fzf --preview $previewer` + return cmd end end diff --git a/src/preview.jl b/src/preview.jl new file mode 100644 index 0000000..94d0111 --- /dev/null +++ b/src/preview.jl @@ -0,0 +1,51 @@ +code = ARGS[1] +code = replace(code, "⏎" => "\n") + +highlighter = nothing +if (highlighter_str = get(ENV, "_INTERACTIVECODESEARCH_JL_HIGHLIGHTER", nothing)) !== nothing + highlighter = @eval @cmd $highlighter_str +end + +file = line = nothing +if (m = match(r"(.*) in \w+ at (.*):([0-9]+)$", code)) !== nothing + # code = String(m[1]) + file = m[2] + line = parse(Int, m[3]) + if isabspath(file) + print(basename(file), " (") + printstyled(file; color = :light_black) + println(")") + else + print(file, " ") + end + println("at line ", line) + if isfile(file) + open(pipeline(highlighter, stdin=IOBuffer(read(file)), stderr=stderr)) do io + width, height = displaysize(stdout) + for (i, str) in enumerate(eachline(io)) + if i > line + height * 2 + close(io) + break + elseif i > line - 2 + if i == line + printstyled(lpad(i, 5), " "; color=:magenta, bold=true) + printstyled(">"; color=:red) + else + print(lpad(i, 5), " ") + printstyled(":"; color=:light_black) + end + print(str) + printstyled("\u200b") # zero-width space + println() + end + end + end + exit() + end +end + +if highlighter === nothing + print(code) +else + run(pipeline(highlighter, stdin=IOBuffer(code), stdout=stdout, stderr=stderr)) +end From cbfa09653cd7e6b73428f8c81467404c389a126d Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 29 Oct 2020 22:00:59 -0400 Subject: [PATCH 3/4] Make it work with Julia < 1.3 --- src/InteractiveCodeSearch.jl | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/InteractiveCodeSearch.jl b/src/InteractiveCodeSearch.jl index 0d19074..318c960 100644 --- a/src/InteractiveCodeSearch.jl +++ b/src/InteractiveCodeSearch.jl @@ -43,12 +43,15 @@ module InteractiveCodeSearch export @search, @searchmethods import Pkg -import fzf_jll using Base using Base: IOError using Compat: addenv using InteractiveUtils: edit, gen_call_with_extracted_types, methodswith +if VERSION >= v"1.3" + import fzf_jll +end + function _readandwrite(cmds) processes = open(cmds, "r+") return (processes.out, processes.in, processes) @@ -440,6 +443,13 @@ macro searchmethods(x) end end +const preferred_terminal = Cmd[ + # Only used in julia < 1.3 + `fzf`, + `peco`, + `percol`, +] + const preferred_gui = Cmd[ `rofi -dmenu -i -p "🔎"`, # what else? @@ -467,13 +477,28 @@ function choose_preferred_command(f, commands::Vector{Cmd}) end end +function _get_fzf_cmd(options) + applicable(fzf_jll.fzf) && return `$(fzf_jll.fzf()) $options` + return fzf_jll.fzf() do cmd + cmd = `$cmd $options` + return setenv(cmd, copy(ENV)) + end +end + function choose_interactive_matcher(; + preferred_terminal = preferred_terminal, preferred_gui = preferred_gui, gui = need_gui()) if gui return choose_preferred_command(preferred_gui) do return preferred_gui[1] end + elseif VERSION < v"1.3" + return choose_preferred_command(preferred_terminal) do + return choose_preferred_command(preferred_gui) do + return preferred_terminal[1] + end + end else preview_jl = joinpath(@__DIR__, "preview.jl") preview_cmd = ` @@ -488,11 +513,12 @@ function choose_interactive_matcher(; if startswith(previewer, '`') && endswith(previewer, '`') previewer = previewer[2:end-1] end - cmd = fzf_jll.fzf() + fzf_options = `` if !occursin("--layout", get(ENV, "FZF_DEFAULT_OPTS", "")) - cmd = `$cmd --layout=reverse` + fzf_options = `$fzf_options --layout=reverse` end - cmd = `$cmd --preview $(previewer * " {}")` + fzf_options = `$fzf_options --preview $(previewer * " {}")` + cmd = _get_fzf_cmd(fzf_options) if Sys.which("pygmentize") !== nothing cmd = addenv(cmd, "_INTERACTIVECODESEARCH_JL_HIGHLIGHTER" => "pygmentize -l jl") end From 5696148408b22ad52587e8681091d0e283e19bff Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 29 Oct 2020 21:00:09 -0400 Subject: [PATCH 4/4] Insert at-search with ')' key --- README.md | 6 +++- src/InteractiveCodeSearch.jl | 6 ++++ src/keybinds.jl | 59 ++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/keybinds.jl diff --git a/README.md b/README.md index c50c1b3..e4b7a6f 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ Search functions returning type `Type` in `Module`s. As this search typically t **Limitations** + * It does not work with Julia >= 1.2. * Running `@searchreturn` for many modules may be slow for the *first* run. Thus, searching from all modules (i.e., not specifying `Module` arguments) may take tens of seconds to minutes, depending of what are loaded. Searching within `Base` takes about 30 seconds. After `DifferentialEquations` is loaded, searching for all modules takes 1.5 minutes. Note that searching from the same module for the second time is fast (a few seconds), even if different `Type` is specified. * The functions must be executed (JIT'ed) once for `@searchreturn` to find their returned by type. * Any IO operations (like printing in REPL) would be slow while the search is active in background. @@ -149,7 +150,8 @@ Configuration interface for `InteractiveCodeSearch`. ```julia using InteractiveCodeSearch -InteractiveCodeSearch.CONFIG.interactive_matcher = `peco` # default in terminal +InteractiveCodeSearch.CONFIG.interactive_matcher = `fzf ...` # default in terminal +InteractiveCodeSearch.CONFIG.interactive_matcher = `peco` InteractiveCodeSearch.CONFIG.interactive_matcher = `percol` InteractiveCodeSearch.CONFIG.interactive_matcher = `rofi -dmenu -i -p "🔎"` # use GUI matcher (default in non-terminal @@ -161,6 +163,8 @@ InteractiveCodeSearch.CONFIG.open = less # use Base.less to read code InteractiveCodeSearch.CONFIG.auto_open = true # default InteractiveCodeSearch.CONFIG.auto_open = false # open matcher even when there # is only one candidate +InteractiveCodeSearch.CONFIG.trigger_key = ')' # insert "@search" on ')' (default) +InteractiveCodeSearch.CONFIG.trigger_key = nothing # disable shortcut ``` **Using InteractiveCodeSearch.jl by default** diff --git a/src/InteractiveCodeSearch.jl b/src/InteractiveCodeSearch.jl index 318c960..66d3c3b 100644 --- a/src/InteractiveCodeSearch.jl +++ b/src/InteractiveCodeSearch.jl @@ -67,6 +67,7 @@ mutable struct SearchConfig # CONFIG open interactive_matcher::Cmd auto_open::Bool + trigger_key::Union{Nothing,Char} end maybe_identifier(s) = !startswith(string(s), "#") @@ -267,6 +268,8 @@ InteractiveCodeSearch.CONFIG.open = less # use Base.less to read code InteractiveCodeSearch.CONFIG.auto_open = true # default InteractiveCodeSearch.CONFIG.auto_open = false # open matcher even when there # is only one candidate +InteractiveCodeSearch.CONFIG.trigger_key = ')' # insert "@search" on ')' (default) +InteractiveCodeSearch.CONFIG.trigger_key = nothing # disable shortcut ``` ## Using InteractiveCodeSearch.jl by default @@ -283,6 +286,7 @@ const CONFIG = SearchConfig( edit, # open `peco`, # interactive_matcher true, # auto_open + ')', # trigger_key ) should_eval(::Any) = false @@ -551,10 +555,12 @@ end function __init__() CONFIG.interactive_matcher = choose_interactive_matcher() + setup_keybinds() end include("taskmanager.jl") include("history.jl") include("return.jl") +include("keybinds.jl") end # module diff --git a/src/keybinds.jl b/src/keybinds.jl new file mode 100644 index 0000000..c194d03 --- /dev/null +++ b/src/keybinds.jl @@ -0,0 +1,59 @@ +using REPL +using REPL: LineEdit + +function setup_keybinds() + schedule(Task(setup_keybinds_background)) + return +end + +function setup_keybinds_background() + try + setup_keybinds_impl() + catch err + @error( + "Unexpected error from `setup_keybinds_impl`", + exception = (err, catch_backtrace()), + ) + end +end + +function setup_keybinds_impl() + # This is why we need https://github.com/JuliaLang/julia/pull/29896... + for _ in 1:20 + try + Base.active_repl.interface.modes[1].keymap_dict + @goto ok + catch + end + sleep(0.05) + end + @warn "Failed to wait for REPL" + return + @label ok + + trigger_key = CONFIG.trigger_key + if trigger_key === nothing + @debug "`trigger_key` is `nothing`; not defining a shortcut key" + return + end + trigger_key::Char + + repl = Base.active_repl + repl isa REPL.LineEditREPL || return + insert_search = function(s, _...) + if isempty(s) || position(LineEdit.buffer(s)) == 0 + LineEdit.edit_insert(s, "@search") + else + LineEdit.edit_insert(s, trigger_key) + end + end + new_keymap = Dict{Any,Any}(trigger_key => insert_search) + + main_mode = repl.interface.modes[1] + main_mode.keymap_dict = LineEdit.keymap_merge(main_mode.keymap_dict, + new_keymap) + return +end + +precompile(Tuple{typeof(setup_keybinds)}) +precompile(Tuple{typeof(setup_keybinds_background)})