diff --git a/NEWS.md b/NEWS.md index 2c185785fe324..5066632468aff 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +Julia v0.6.0 Release Notes +========================== + +* Some automatic completion of brackets and quotations has been added to the REPL and are enabled by default. These can be disabled or enabled with the function `Base.REPL.enable_bracketmatch(enable::Bool)`. + Julia v0.5.0 Release Notes ========================== diff --git a/base/REPL.jl b/base/REPL.jl index 0ad9968178ce8..cd5f88ea0ad86 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -30,7 +30,12 @@ import ..LineEdit: history_prev_prefix, history_search, accept_result, - terminal + terminal, + buffer, + edit_insert, + edit_move_right, + edit_move_left, + edit_backspace abstract AbstractREPL @@ -692,6 +697,9 @@ end repl_filename(repl, hp::REPLHistoryProvider) = "REPL[$(length(hp.history)-hp.start_idx)]" repl_filename(repl, hp) = "REPL" +const AUTOMATIC_BRACKET_MATCH = Ref(true) +enable_bracketmatch(b::Bool) = AUTOMATIC_BRACKET_MATCH[] = b + function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_repl_keymap = Dict{Any,Any}[]) ### # @@ -873,6 +881,73 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep end, ) + # Convenience completions for brackets and single/double quote + peek(b::IOBuffer) = (p = position(b); c = read(b, Char); seek(b, p); return c) + leftpeek(b::IOBuffer) = (p = position(b); c = LineEdit.char_move_left(b); seek(b, p); return c) + left_brackets = ['(', '{', '['] + right_brackets = [')', '}', ']'] + right_brackets_ws = [')', '}', ']', ' ', '\t'] + all_brackets_ws = vcat(left_brackets, right_brackets_ws) + for (l, r) in zip(left_brackets, right_brackets) + # If we enter a left bracket automatically complete it if the next + # char is a whitespace or a right bracket + repl_keymap[l] = (s, o...) -> begin + edit_insert(s, l) + if AUTOMATIC_BRACKET_MATCH[] && (eof(buffer(s)) || peek(buffer(s)) in right_brackets_ws) + edit_insert(s, r) + edit_move_left(s) + end + end + # If we enter a right bracket and the next char is that right bracket just move right + repl_keymap[r] = (s, o...) -> begin + if AUTOMATIC_BRACKET_MATCH[] && !eof(buffer(s)) && peek(buffer(s)) == r + edit_move_right(s) + else + edit_insert(s, r) + end + end + end + + # Similar to above but with quotation marks that need to be handled a bit differently + for v in ['\"', '\''] + repl_keymap[v] = (s, o...) -> begin + b = buffer(s) + # Next char is the quote symbol so just move right + if AUTOMATIC_BRACKET_MATCH[] && !eof(b) && peek(b) == v + edit_move_right(s) + # Prev char or next char is not whitespace + elseif AUTOMATIC_BRACKET_MATCH[] && + ((position(b) > 0 && leftpeek(b) in all_brackets_ws) || + (!eof(b) && peek(b) in right_brackets_ws) || + b.size == 0) + edit_insert(s, v) + edit_insert(s, v) + edit_move_left(s) + else + edit_insert(s, v) + end + end + end + + # On backspace, also remove a corresponding right bracket + # to the right if we remove a left bracket + left_brackets2 = ['(', '{', '[', '\"', '\''] + right_brackets2 = [')', '}', ']', '\"', '\''] + repl_keymap['\b'] = (s, data, c) -> begin + b = buffer(s) + str = String(b) + if AUTOMATIC_BRACKET_MATCH[] && !eof(buffer(s)) + i = findfirst(left_brackets2, str[prevind(str, position(b) + 1)]) + if i != 0 && peek(b) == right_brackets2[i] + edit_move_right(s) + edit_backspace(s) + edit_backspace(s) + return + end + end + edit_backspace(s) + end + prefix_prompt, prefix_keymap = LineEdit.setup_prefix_keymap(hp, julia_prompt) a = Dict{Any,Any}[skeymap, repl_keymap, prefix_keymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults] diff --git a/test/repl.jl b/test/repl.jl index f75411b165b58..df90ec58147dc 100644 --- a/test/repl.jl +++ b/test/repl.jl @@ -226,7 +226,6 @@ fakehistory = """ # mode: julia \t2 + 2 """ - # Test various history related issues begin stdin_write, stdout_read, stdout_read, repl = fake_repl() @@ -397,6 +396,52 @@ begin # Try entering search mode while in custom repl mode LineEdit.enter_search(s, custom_histp, true) end + +# Test some completion features of the REPL with brackets +begin + stdin_write, stdout_read, stderr_read, repl = fake_repl() + + repl.interface = REPL.setup_interface(repl) + repl_mode = repl.interface.modes[1] + shell_mode = repl.interface.modes[2] + help_mode = repl.interface.modes[3] + + repltask = @async begin + Base.REPL.run_repl(repl) + end + + c = Condition() + + sendrepl3 = cmd -> write(stdin_write,"$cmd\n notify(c)\n") + + # Test [B, =, (, 1, rightarrow enter] + sendrepl3("B = (1,\e[C \n") + wait(c) + @test B == (1,) + + # Test [B, =, ", f, rightarrow enter] + sendrepl3("B = \"f\e[C \n") + wait(c) + @test B == "f" + # Test [B. =. (, (, (, 1, enter] + sendrepl3("B = (((1\n") + wait(c) + @test B == 1 + + # Test [(, ", , backspace, backspace, B, =, 5] + # The autocompleted bracers should be removed by backspace + sendrepl3("(\"\b\bB = 5\n") + wait(c) + @test B == 5 + + sendrepl3("\\sigma\t\\Sigma\t\e[D\b\e[C=3") + wait(c) + @test Σ == 3 + # close repl + write(stdin_write, '\x04') + wait(repltask) +end + # Simple non-standard REPL tests if !is_windows() || Sys.windows_version() >= Sys.WINDOWS_VISTA_VER stdin_write, stdout_read, stdout_read, repl = fake_repl()