From eae45d60c5b539d75ea96fd2b9708da8a28a44d6 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Sat, 19 Aug 2017 10:09:44 +0200 Subject: [PATCH 1/2] introduce unsafe_{chr2ind,ind2chr} * these allow to request the position past the end (at least for `String`); this is useful when working with `IOBuffer`, which has one more position than there are valid indexes in the corresponding `String`. * ind2chr becomes slightly faster as a result of removing one unnecessary `next` call * the main logic of these functions is factored out into one single function --- base/strings/basic.jl | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/base/strings/basic.jl b/base/strings/basic.jl index 6609cd1417b96..d332ecb528f2f 100644 --- a/base/strings/basic.jl +++ b/base/strings/basic.jl @@ -277,16 +277,7 @@ julia> chr2ind(str, 2) """ function ind2chr(s::AbstractString, i::Integer) s[i] # throws error if invalid - j = 1 - k = start(s) - while true - c, l = next(s,k) - if i <= k - return j - end - j += 1 - k = l - end + unsafe_ind2chr(s, i) end """ @@ -309,18 +300,25 @@ julia> ind2chr(str, 3) """ function chr2ind(s::AbstractString, i::Integer) i < start(s) && throw(BoundsError(s, i)) + k = unsafe_chr2ind(s, i) + s[k] # throws error if invalid + k +end + +function map_chr_ind(s::AbstractString, i::Integer, stop, ret) j = 1 k = start(s) while true - c, l = next(s,k) - if i == j - return k - end + i == stop((j, k)) && return ret((j, k)) # k could point after the last character + _, k = next(s, k) j += 1 - k = l end end +unsafe_ind2chr(s::AbstractString, i::Integer) = map_chr_ind(s, i, last, first) +unsafe_chr2ind(s::AbstractString, i::Integer) = map_chr_ind(s, i, first, last) + + struct EachStringIndex{T<:AbstractString} s::T end From 5838d5a301733f17ed2d8b500c52f19e81e46a92 Mon Sep 17 00:00:00 2001 From: Rafael Fourquet Date: Sat, 19 Aug 2017 10:25:40 +0200 Subject: [PATCH 2/2] REPL: transpose words with Alt-t Fixes part of #8447. --- base/repl/LineEdit.jl | 34 ++++++++++++++++++++++--- test/lineedit.jl | 58 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 14 deletions(-) diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index 2ee272e43a6aa..6bf65416526db 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -587,8 +587,9 @@ function edit_kill_line(s::MIState) refresh_line(s) end -edit_transpose(s) = edit_transpose(buffer(s)) && refresh_line(s) -function edit_transpose(buf::IOBuffer) +edit_transpose_chars(s) = edit_transpose_chars(buffer(s)) && refresh_line(s) + +function edit_transpose_chars(buf::IOBuffer) position(buf) == 0 && return false eof(buf) && char_move_left(buf) char_move_left(buf) @@ -599,6 +600,32 @@ function edit_transpose(buf::IOBuffer) return true end +edit_transpose_words(s) = edit_transpose_words(buffer(s)) && refresh_line(s) + +function edit_transpose_words(buf::IOBuffer, mode=:emacs) + mode in [:readline, :emacs] || + throw(ArgumentError("`mode` must be `:readline` or `:emacs`")) + pos = position(buf) + if mode == :emacs + char_move_word_left(buf) + char_move_word_right(buf) + end + char_move_word_right(buf) + e2 = position(buf) + char_move_word_left(buf) + b2 = position(buf) + char_move_word_left(buf) + b1 = position(buf) + char_move_word_right(buf) + e1 = position(buf) + e1 >= b2 && (seek(buf, pos); return false) + word2 = splice!(buf.data, b2+1:e2, buf.data[b1+1:e1]) + splice!(buf.data, b1+1:e1, word2) + seek(buf, e2) + true +end + + edit_clear(buf::IOBuffer) = truncate(buf, 0) function edit_clear(s::MIState) @@ -1492,7 +1519,8 @@ AnyDict( input = bracketed_paste(s) edit_insert(s, input) end, - "^T" => (s,o...)->edit_transpose(s) + "^T" => (s,o...)->edit_transpose_chars(s), + "\et" => (s,o...)->edit_transpose_words(s), ) const history_keymap = AnyDict( diff --git a/test/lineedit.jl b/test/lineedit.jl index c882dc5dc586d..3d2778e8212cf 100644 --- a/test/lineedit.jl +++ b/test/lineedit.jl @@ -308,41 +308,77 @@ let buf = IOBuffer() @test String(buf.data[1:buf.size]) == "a" end -## edit_transpose ## +## edit_transpose_chars ## let buf = IOBuffer() LineEdit.edit_insert(buf, "abcde") seek(buf,0) - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "abcde" LineEdit.char_move_right(buf) - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "bacde" - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "bcade" seekend(buf) - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "bcaed" - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "bcade" seek(buf, 0) LineEdit.edit_clear(buf) LineEdit.edit_insert(buf, "αβγδε") seek(buf,0) - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "αβγδε" LineEdit.char_move_right(buf) - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "βαγδε" - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "βγαδε" seekend(buf) - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "βγαεδ" - LineEdit.edit_transpose(buf) + LineEdit.edit_transpose_chars(buf) @test String(buf.data[1:buf.size]) == "βγαδε" end +@testset "edit_word_transpose" begin + buf = IOBuffer() + mode = Ref{Symbol}() + function transpose!(i) # i: char indice + seek(buf, Base.unsafe_chr2ind(String(take!(copy(buf))), i+1)-1) + LineEdit.edit_transpose_words(buf, mode[]) + str = String(take!(copy(buf))) + str, Base.unsafe_ind2chr(str, position(buf)+1)-1 + end + + mode[] = :readline + LineEdit.edit_insert(buf, "àbç def gh ") + @test transpose!(0) == ("àbç def gh ", 0) + @test transpose!(1) == ("àbç def gh ", 1) + @test transpose!(2) == ("àbç def gh ", 2) + @test transpose!(3) == ("def àbç gh ", 7) + @test transpose!(4) == ("àbç def gh ", 7) + @test transpose!(5) == ("def àbç gh ", 7) + @test transpose!(6) == ("àbç def gh ", 7) + @test transpose!(7) == ("àbç gh def ", 11) + @test transpose!(10) == ("àbç def gh ", 11) + @test transpose!(11) == ("àbç gh def", 12) + LineEdit.edit_insert(buf, " ") + @test transpose!(13) == ("àbç def gh", 13) + + take!(buf) + mode[] = :emacs + LineEdit.edit_insert(buf, "àbç def gh ") + @test transpose!(0) == ("def àbç gh ", 7) + @test transpose!(4) == ("àbç def gh ", 7) + @test transpose!(5) == ("àbç gh def ", 11) + @test transpose!(10) == ("àbç def gh", 12) + LineEdit.edit_insert(buf, " ") + @test transpose!(13) == ("àbç gh def", 13) +end + let term = TestHelpers.FakeTerminal(IOBuffer(), IOBuffer(), IOBuffer()) s = LineEdit.init_state(term, ModalInterface([Prompt("test> ")]))