From f1e309bbd1501e38216caa81dd37f200940480b2 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Fri, 14 Jul 2017 13:48:11 +0200 Subject: [PATCH 1/5] allow a function instead of a string as Prompt.prompt --- base/repl/LineEdit.jl | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index 941a184ce0743..dcd04873af2e6 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -19,7 +19,8 @@ struct ModalInterface <: TextInterface end mutable struct Prompt <: TextInterface - prompt::String + # A string or function to be printed as the prompt. + prompt::Union{String,Function} # A string or function to be printed before the prompt. May not change the length of the prompt. # This may be used for changing the color, issuing other terminal escape codes, etc. prompt_prefix::Union{String,Function} @@ -34,7 +35,7 @@ mutable struct Prompt <: TextInterface sticky::Bool end -show(io::IO, x::Prompt) = show(io, string("Prompt(\"", x.prompt, "\",...)")) +show(io::IO, x::Prompt) = show(io, string("Prompt(\"", prompt_string(x.prompt), "\",...)")) mutable struct MIState interface::ModalInterface @@ -182,8 +183,10 @@ function _clear_input_area(terminal, state::InputAreaState) clear_line(terminal) end -prompt_string(s::PromptState) = s.p.prompt +prompt_string(s::PromptState) = prompt_string(s.p) +prompt_string(p::Prompt) = prompt_string(p.prompt) prompt_string(s::AbstractString) = s +prompt_string(f::Function) = eval(Expr(:call, f)) refresh_multi_line(s::ModeState) = refresh_multi_line(terminal(s), s) refresh_multi_line(termbuf::TerminalBuffer, s::ModeState) = refresh_multi_line(termbuf, terminal(s), s) @@ -454,7 +457,7 @@ function edit_insert(s::PromptState, c) end str = string(c) edit_insert(buf, str) - offset = s.ias.curs_row == 1 ? sizeof(s.p.prompt) : s.indent + offset = s.ias.curs_row == 1 ? sizeof(prompt_string(s.p.prompt)) : s.indent if !('\n' in str) && eof(buf) && ((line_size() + offset + sizeof(str) - 1) < width(terminal(s))) # Avoid full update when appending characters to the end @@ -627,11 +630,11 @@ function write_prompt(terminal, p::Prompt) suffix = isa(p.prompt_suffix,Function) ? eval(Expr(:call, p.prompt_suffix)) : p.prompt_suffix write(terminal, prefix) write(terminal, Base.text_colors[:bold]) - write(terminal, p.prompt) + write(terminal, prompt_string(p.prompt)) write(terminal, Base.text_colors[:normal]) write(terminal, suffix) end -write_prompt(terminal, s::String) = write(terminal, s) +write_prompt(terminal, s::Union{AbstractString,Function}) = write(terminal, prompt_string(s)) ### Keymap Support @@ -1062,7 +1065,7 @@ PrefixHistoryPrompt(hp::T, parent_prompt) where T<:HistoryProvider = PrefixHisto init_state(terminal, p::PrefixHistoryPrompt) = PrefixSearchState(terminal, p, "", IOBuffer()) write_prompt(terminal, s::PrefixSearchState) = write_prompt(terminal, s.histprompt.parent_prompt) -prompt_string(s::PrefixSearchState) = s.histprompt.parent_prompt.prompt +prompt_string(s::PrefixSearchState) = prompt_string(s.histprompt.parent_prompt.prompt) terminal(s::PrefixSearchState) = s.terminal @@ -1559,7 +1562,9 @@ end run_interface(::Prompt) = nothing -init_state(terminal, prompt::Prompt) = PromptState(terminal, prompt, IOBuffer(), InputAreaState(1, 1), #=indent(spaces)=#strwidth(prompt.prompt)) +init_state(terminal, prompt::Prompt) = + PromptState(terminal, prompt, IOBuffer(), InputAreaState(1, 1), + #=indent(spaces)=# strwidth(prompt_string(prompt.prompt))) function init_state(terminal, m::ModalInterface) s = MIState(m, m.modes[1], false, Dict{Any,Any}()) From ed523b5e7e38f0cbe81cd0348e00c24dec235ac8 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Sun, 16 Jul 2017 11:14:01 +0200 Subject: [PATCH 2/5] add tests --- test/repl.jl | 88 +++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/test/repl.jl b/test/repl.jl index 1e33a5ec0221d..458fb6a48146f 100644 --- a/test/repl.jl +++ b/test/repl.jl @@ -214,9 +214,9 @@ function buffercontents(buf::IOBuffer) c end -function AddCustomMode(repl) +function AddCustomMode(repl, prompt) # Custom REPL mode tests - foobar_mode = LineEdit.Prompt("TestΠ"; + foobar_mode = LineEdit.Prompt(prompt; prompt_prefix="\e[38;5;166m", prompt_suffix=Base.text_colors[:white], on_enter = s->true, @@ -289,7 +289,7 @@ fakehistory = """ """ # Test various history related issues -begin +for prompt = ["TestΠ", () -> randstring(rand(1:10))] stdin_write, stdout_read, stdout_read, repl = fake_repl() # In the future if we want we can add a test that the right object # gets displayed by intercepting the display @@ -438,7 +438,7 @@ begin # Test that new modes can be dynamically added to the REPL and will # integrate nicely - foobar_mode, custom_histp = AddCustomMode(repl) + foobar_mode, custom_histp = AddCustomMode(repl, prompt) # ^R l, should now find `ls` in foobar mode LineEdit.enter_search(s, histp, true) @@ -535,43 +535,46 @@ end # Simple non-standard REPL tests if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER - stdin_write, stdout_read, stdout_read, repl = fake_repl() - panel = LineEdit.Prompt("testπ"; - prompt_prefix="\e[38;5;166m", - prompt_suffix=Base.text_colors[:white], - on_enter = s->true) - - hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:parse => panel)) - search_prompt, skeymap = LineEdit.setup_prefix_keymap(hp, panel) - REPL.history_reset_state(hp) - - panel.hist = hp - panel.keymap_dict = LineEdit.keymap(Dict{Any,Any}[skeymap, - LineEdit.default_keymap, LineEdit.escape_defaults]) - - c = Condition() - panel.on_done = (s,buf,ok)->begin - if !ok - LineEdit.transition(s,:abort) + for prompt = ["testπ", () -> randstring(rand(1:10))] + stdin_write, stdout_read, stdout_read, repl = fake_repl() + panel = LineEdit.Prompt(prompt; + prompt_prefix="\e[38;5;166m", + prompt_suffix=Base.text_colors[:white], + on_enter = s->true) + + hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:parse => panel)) + search_prompt, skeymap = LineEdit.setup_prefix_keymap(hp, panel) + REPL.history_reset_state(hp) + + panel.hist = hp + panel.keymap_dict = LineEdit.keymap(Dict{Any,Any}[skeymap, + LineEdit.default_keymap, LineEdit.escape_defaults]) + + c = Condition() + panel.on_done = (s,buf,ok)->begin + if !ok + LineEdit.transition(s,:abort) + end + line = strip(String(take!(buf))) + LineEdit.reset_state(s) + return notify(c,line) end - line = strip(String(take!(buf))) - LineEdit.reset_state(s) - return notify(c,line) - end - - repltask = @async Base.REPL.run_interface(repl.t, LineEdit.ModalInterface([panel,search_prompt])) - write(stdin_write,"a\n") - @test wait(c) == "a" - # Up arrow enter should recall history even at the start - write(stdin_write,"\e[A\n") - @test wait(c) == "a" - # And again - write(stdin_write,"\e[A\n") - @test wait(c) == "a" - # Close REPL ^D - write(stdin_write, '\x04') - wait(repltask) + repltask = @async Base.REPL.run_interface(repl.t, + LineEdit.ModalInterface([panel,search_prompt])) + + write(stdin_write,"a\n") + @test wait(c) == "a" + # Up arrow enter should recall history even at the start + write(stdin_write,"\e[A\n") + @test wait(c) == "a" + # And again + write(stdin_write,"\e[A\n") + @test wait(c) == "a" + # Close REPL ^D + write(stdin_write, '\x04') + wait(repltask) + end end ccall(:jl_exit_on_sigint, Void, (Cint,), 1) @@ -713,14 +716,15 @@ const altkeys = [Dict{Any,Any}("\e[A" => (s,o...)->(LineEdit.edit_move_up(s) || if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER - for keys = [altkeys, merge(altkeys...)] + for keys = [altkeys, merge(altkeys...)], + altprompt = ["julia-$(VERSION.major).$(VERSION.minor)> ", + () -> "julia-$(Base.GIT_VERSION_INFO.commit_short)"] histfile = tempname() try stdin_write, stdout_read, stderr_read, repl = fake_repl() repl.specialdisplay = Base.REPL.REPLDisplay(repl) repl.history_file = true - altprompt = "julia-$(VERSION.major).$(VERSION.minor)> " withenv("JULIA_HISTORY" => histfile) do repl.interface = REPL.setup_interface(repl, extra_repl_keymap = altkeys) end @@ -747,7 +751,7 @@ if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER # Check that the correct prompt was displayed output = readuntil(stdout_read, "1 * 1;") - @test !isempty(search(output, altprompt)) + @test !isempty(search(output, LineEdit.prompt_string(altprompt))) @test isempty(search(output, "julia> ")) # Check the history file From a127098b9a9a7f716e8b2994a5759c1669d290cb Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Wed, 19 Jul 2017 10:27:21 +0200 Subject: [PATCH 3/5] remove one added test (causing broken pipe) --- test/repl.jl | 73 +++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/test/repl.jl b/test/repl.jl index 458fb6a48146f..10f4ff931204d 100644 --- a/test/repl.jl +++ b/test/repl.jl @@ -535,46 +535,43 @@ end # Simple non-standard REPL tests if !Sys.iswindows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER - for prompt = ["testπ", () -> randstring(rand(1:10))] - stdin_write, stdout_read, stdout_read, repl = fake_repl() - panel = LineEdit.Prompt(prompt; - prompt_prefix="\e[38;5;166m", - prompt_suffix=Base.text_colors[:white], - on_enter = s->true) - - hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:parse => panel)) - search_prompt, skeymap = LineEdit.setup_prefix_keymap(hp, panel) - REPL.history_reset_state(hp) - - panel.hist = hp - panel.keymap_dict = LineEdit.keymap(Dict{Any,Any}[skeymap, - LineEdit.default_keymap, LineEdit.escape_defaults]) - - c = Condition() - panel.on_done = (s,buf,ok)->begin - if !ok - LineEdit.transition(s,:abort) - end - line = strip(String(take!(buf))) - LineEdit.reset_state(s) - return notify(c,line) - end + stdin_write, stdout_read, stdout_read, repl = fake_repl() + panel = LineEdit.Prompt("testπ"; + prompt_prefix="\e[38;5;166m", + prompt_suffix=Base.text_colors[:white], + on_enter = s->true) + + hp = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:parse => panel)) + search_prompt, skeymap = LineEdit.setup_prefix_keymap(hp, panel) + REPL.history_reset_state(hp) + + panel.hist = hp + panel.keymap_dict = LineEdit.keymap(Dict{Any,Any}[skeymap, + LineEdit.default_keymap, LineEdit.escape_defaults]) - repltask = @async Base.REPL.run_interface(repl.t, - LineEdit.ModalInterface([panel,search_prompt])) - - write(stdin_write,"a\n") - @test wait(c) == "a" - # Up arrow enter should recall history even at the start - write(stdin_write,"\e[A\n") - @test wait(c) == "a" - # And again - write(stdin_write,"\e[A\n") - @test wait(c) == "a" - # Close REPL ^D - write(stdin_write, '\x04') - wait(repltask) + c = Condition() + panel.on_done = (s,buf,ok)->begin + if !ok + LineEdit.transition(s,:abort) + end + line = strip(String(take!(buf))) + LineEdit.reset_state(s) + return notify(c,line) end + + repltask = @async Base.REPL.run_interface(repl.t, LineEdit.ModalInterface([panel,search_prompt])) + + write(stdin_write,"a\n") + @test wait(c) == "a" + # Up arrow enter should recall history even at the start + write(stdin_write,"\e[A\n") + @test wait(c) == "a" + # And again + write(stdin_write,"\e[A\n") + @test wait(c) == "a" + # Close REPL ^D + write(stdin_write, '\x04') + wait(repltask) end ccall(:jl_exit_on_sigint, Void, (Cint,), 1) From 0b0b614c610e55da49454917ec1a75b8534a9871 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Mon, 24 Jul 2017 14:54:37 +0200 Subject: [PATCH 4/5] use invokelatest instead of call (tkelman) --- base/repl/LineEdit.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index dcd04873af2e6..5d0ed96d1f0f1 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -186,7 +186,7 @@ end prompt_string(s::PromptState) = prompt_string(s.p) prompt_string(p::Prompt) = prompt_string(p.prompt) prompt_string(s::AbstractString) = s -prompt_string(f::Function) = eval(Expr(:call, f)) +prompt_string(f::Function) = Base.invokelatest(f) refresh_multi_line(s::ModeState) = refresh_multi_line(terminal(s), s) refresh_multi_line(termbuf::TerminalBuffer, s::ModeState) = refresh_multi_line(termbuf, terminal(s), s) @@ -626,8 +626,8 @@ default_enter_cb(_) = true write_prompt(terminal, s::PromptState) = write_prompt(terminal, s.p) function write_prompt(terminal, p::Prompt) - prefix = isa(p.prompt_prefix,Function) ? eval(Expr(:call, p.prompt_prefix)) : p.prompt_prefix - suffix = isa(p.prompt_suffix,Function) ? eval(Expr(:call, p.prompt_suffix)) : p.prompt_suffix + prefix = prompt_string(p.prompt_prefix) + suffix = prompt_string(p.prompt_suffix) write(terminal, prefix) write(terminal, Base.text_colors[:bold]) write(terminal, prompt_string(p.prompt)) From 2f739354d383790f285c70fd57393012e814bc3e Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Thu, 27 Jul 2017 14:53:39 +0200 Subject: [PATCH 5/5] simplify prompt to prompt.prompt as argument to prompt_string --- base/repl/LineEdit.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index 5d0ed96d1f0f1..2e6c4c36acee0 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -274,7 +274,7 @@ end # Edit functionality -is_non_word_char(c) = c in " \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~" +is_non_word_char(c) = c in """ \t\n\"\\'`@\$><=:;|&{}()[].,+-*/?%^~""" function reset_key_repeats(f::Function, s::MIState) key_repeats_sav = s.key_repeats @@ -1564,7 +1564,7 @@ run_interface(::Prompt) = nothing init_state(terminal, prompt::Prompt) = PromptState(terminal, prompt, IOBuffer(), InputAreaState(1, 1), - #=indent(spaces)=# strwidth(prompt_string(prompt.prompt))) + #=indent(spaces)=# strwidth(prompt_string(prompt))) function init_state(terminal, m::ModalInterface) s = MIState(m, m.modes[1], false, Dict{Any,Any}())