Skip to content

Commit

Permalink
tab: jump to end of line; backspace: try to keep right side aligned
Browse files Browse the repository at this point in the history
  • Loading branch information
rfourquet committed Aug 5, 2017
1 parent 3e61d20 commit 3d84df3
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 22 deletions.
77 changes: 60 additions & 17 deletions base/repl/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -478,26 +478,42 @@ function edit_insert(buf::IOBuffer, c)
end
end

function edit_backspace(s::PromptState, multispaces::Bool=false)
if edit_backspace(s.input_buffer, multispaces)
function edit_backspace(s::PromptState, align::Bool=false, adjust=align)
if edit_backspace(s.input_buffer, align)
refresh_line(s)
else
beep(terminal(s))
end
end

function edit_backspace(buf::IOBuffer, multispaces::Bool=false)
const _newline = UInt8('\n')
const _space = UInt8(' ')

# align: delete up to 4 spaces to align to a multiple of 4 chars
# adjust: also delete spaces to the right of the cursor to try to keep aligned what is
# on the right
function edit_backspace(buf::IOBuffer, align::Bool=false, adjust::Bool=align)
!align && adjust &&
throw(DomainError((align, adjust),
"if `adjust` is `true`, `align` must be `true`"))
oldpos = position(buf)
if oldpos > 0
c = char_move_left(buf)
newpos = position(buf)
if multispaces && c == ' ' # maybe delete multiple spaces
beg = rsearch(buf.data, '\n', newpos)
if align && c == ' ' # maybe delete multiple spaces
beg = beginofline(buf, newpos)
align = strwidth(String(buf.data[1+beg:newpos])) % 4
nonspace = findprev(c -> c != UInt8(' '), buf.data, newpos)
nonspace = findprev(c -> c != _space, buf.data, newpos)
if newpos-align >= nonspace
newpos = newpos-align
newpos -= align
seek(buf, newpos)
if adjust
spaces = findnext(c -> c!= _space,
buf.data[newpos+2:buf.size], 1)
oldpos = spaces == 0 ? buf.size :
buf.data[newpos+1+spaces] == _newline ? newpos+spaces :
newpos + min(spaces, 4)
end
end
end
splice_buffer!(buf, newpos:oldpos-1)
Expand Down Expand Up @@ -1340,33 +1356,60 @@ function bracketed_paste(s)
return replace(input, '\t', " "^tabwidth)
end

function edit_tab(s)
buf = buffer(s)
beginofline(buf, pos=position(buf)) = findprev(buf.data, '\n' % UInt8, pos)

function endofline(buf, pos=position(buf))
eol = findnext(buf.data[pos+1:buf.size], '\n' % UInt8, 1)
eol == 0 ? buf.size : pos + eol - 1
end

# jump_spaces: if cursor is on a ' ', move it to the first non-' ' char on the right
# if `delete_trailing`, ignore trailing ' ' by deleting them
function edit_tab(s, jump_spaces=false, delete_trailing=jump_spaces)
# Yes, we are ignoring the possiblity
# the we could be in the middle of a multi-byte
# sequence, here but that's ok, since any
# whitespace we're interested in is only one byte
buf = buffer(s)
i = position(buf)
if i != 0
c = buf.data[i]
if c == UInt8('\n') || c == UInt8('\t') ||
if i != 0 &&
let c = buf.data[i]
c == UInt8('\n') || c == UInt8('\t') ||
# hack to allow path completion in cmds
# after a space, e.g., `cd <tab>`, while still
# allowing multiple indent levels
(c == UInt8(' ') && i > 3 && buf.data[i-1] == UInt8(' '))
beg = rsearch(buf.data, '\n', i)
align = 4 - strwidth(String(buf.data[1+beg:i])) % 4 # align to multiples of 4
return edit_insert(s, " "^align)
end
edit_tab(buf, jump_spaces, delete_trailing)
else
complete_line(s)
end
complete_line(s)
refresh_line(s)
end

function Base.LineEdit.edit_tab(buf::IOBuffer,
jump_spaces=false, delete_trailing=jump_spaces)
i = position(buf)
if jump_spaces && i < buf.size && buf.data[i+1] == _space
spaces = findnext(c -> c != _space,
buf.data[i+1:buf.size], 1)
if delete_trailing && (spaces == 0 || buf.data[i+spaces] == _newline)
splice_buffer!(buf, i:(spaces == 0 ? buf.size-1 : i+spaces-2))
else
jump = spaces == 0 ? buf.size : i+spaces-1
return seek(buf, jump)
end
end
# align to multiples of 4:
align = 4 - strwidth(String(buf.data[1+beginofline(buf,i):i])) % 4
return edit_insert(buf, ' '^align)
end


const default_keymap =
AnyDict(
# Tab
'\t' => (s,o...)->edit_tab(s),
'\t' => (s,o...)->edit_tab(s, true),
# Enter
'\r' => (s,o...)->begin
if on_enter(s) || (eof(buffer(s)) && s.key_repeats > 1)
Expand Down
57 changes: 52 additions & 5 deletions test/lineedit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -409,28 +409,75 @@ end
buf = LineEdit.buffer(s)
String(buf.data[1:buf.size])
end
move_left(s, n) = for x = 1:n
LineEdit.edit_move_left(s)
end

bufpos(s::Base.LineEdit.MIState) = position(LineEdit.buffer(s))

LineEdit.edit_insert(s, "for x=1:10\n")
LineEdit.edit_tab(s)
@test bufferdata(s) == "for x=1:10\n "
LineEdit.edit_backspace(s, true)
LineEdit.edit_backspace(s, true, false)
@test bufferdata(s) == "for x=1:10\n"
LineEdit.edit_insert(s, " ")
@test bufpos(s) == 13
LineEdit.edit_tab(s)
@test bufferdata(s) == "for x=1:10\n "
LineEdit.edit_insert(s, " ")
LineEdit.edit_backspace(s, true)
LineEdit.edit_backspace(s, true, false)
@test bufferdata(s) == "for x=1:10\n "
LineEdit.edit_insert(s, "éé=3 ")
LineEdit.edit_tab(s)
@test bufferdata(s) == "for x=1:10\n éé=3 "
LineEdit.edit_backspace(s, true)
LineEdit.edit_backspace(s, true, false)
@test bufferdata(s) == "for x=1:10\n éé=3"
LineEdit.edit_insert(s, "\n 1∉x ")
LineEdit.edit_tab(s)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
LineEdit.edit_backspace(s, false)
LineEdit.edit_backspace(s, false, false)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
LineEdit.edit_backspace(s, true)
LineEdit.edit_backspace(s, true, false)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
LineEdit.edit_move_word_left(s)
LineEdit.edit_tab(s)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
LineEdit.move_line_start(s)
@test bufpos(s) == 22
LineEdit.edit_tab(s, true)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
@test bufpos(s) == 30
LineEdit.edit_move_left(s)
@test bufpos(s) == 29
LineEdit.edit_backspace(s, true, true)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
@test bufpos(s) == 26
LineEdit.edit_tab(s, false) # same as edit_tab(s, true) here
@test bufpos(s) == 30
move_left(s, 6)
@test bufpos(s) == 24
LineEdit.edit_backspace(s, true, true)
@test bufferdata(s) == "for x=1:10\n éé=3\n 1∉x "
@test bufpos(s) == 22
LineEdit.edit_kill_line(s)
LineEdit.edit_insert(s, ' '^10)
move_left(s, 7)
@test bufferdata(s) == "for x=1:10\n éé=3\n "
@test bufpos(s) == 25
LineEdit.edit_tab(s, true, false)
@test bufpos(s) == 32
move_left(s, 7)
LineEdit.edit_tab(s, true, true)
@test bufpos(s) == 26
@test bufferdata(s) == "for x=1:10\n éé=3\n "
# test again the same, when there is a next line
LineEdit.edit_insert(s, " \nend")
move_left(s, 11)
@test bufpos(s) == 25
LineEdit.edit_tab(s, true, false)
@test bufpos(s) == 32
move_left(s, 7)
LineEdit.edit_tab(s, true, true)
@test bufpos(s) == 26
@test bufferdata(s) == "for x=1:10\n éé=3\n \nend"
end

0 comments on commit 3d84df3

Please sign in to comment.