Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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
==========================

Expand Down
77 changes: 76 additions & 1 deletion base/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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}[])
###
#
Expand Down Expand Up @@ -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 ['\"', '\'']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should matching also apply to back ticks?

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]
Expand Down
47 changes: 46 additions & 1 deletion test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ fakehistory = """
# mode: julia
\t2 + 2
"""

# Test various history related issues
begin
stdin_write, stdout_read, stdout_read, repl = fake_repl()
Expand Down Expand Up @@ -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()
Expand Down