From 3d84df310a603c7fa2faebffd9becdf6f0e1f560 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Wed, 2 Aug 2017 17:23:44 +0200 Subject: [PATCH] tab: jump to end of line; backspace: try to keep right side aligned --- base/repl/LineEdit.jl | 77 +++++++++++++++++++++++++++++++++---------- test/lineedit.jl | 57 +++++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 22 deletions(-) diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index eba0d00ca5c95..4ab248b97e98d 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -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) @@ -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 `, 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) diff --git a/test/lineedit.jl b/test/lineedit.jl index db08673822230..b3a4cc3dd81b9 100644 --- a/test/lineedit.jl +++ b/test/lineedit.jl @@ -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