Skip to content

Commit

Permalink
REPL: add few "region" operations
Browse files Browse the repository at this point in the history
* add possibility to set the "mark" in the edit area, with C-Space
* the mark and current position (the "point") delimit a region
* C-x C-x exchanges the mark with the point
* M-w is set to copy the region into the kill buffer
* M-W is set to kill the region into the kill buffer
  (in Emacs, the binding for this is C-w, but it's already taken
  by edit_werase)
  • Loading branch information
rfourquet committed Aug 21, 2017
1 parent 2a6e0d5 commit 8b2bef8
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 18 deletions.
81 changes: 67 additions & 14 deletions base/repl/LineEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,19 @@ mutable struct PromptState <: ModeState
indent::Int
end

setmark(s) = mark(buffer(s))

# the default mark is 0
getmark(s) = max(0, buffer(s).mark)

# given two buffer positions delimitating a region
# as an close-open range, return a range of the included
# positions, 0-based (suitable for splice_buffer!)
region(a::Int, b::Int) = ((a, b) = minmax(a, b); a:b-1)
region(s) = region(getmark(s), position(buffer(s)))

const REGION_ANIMATION_DURATION = Ref(.2)

input_string(s::PromptState) = String(take!(copy(s.input_buffer)))

input_string_newlines(s::PromptState) = count(c->(c == '\n'), input_string(s))
Expand Down Expand Up @@ -286,6 +299,18 @@ function reset_key_repeats(f::Function, s::MIState)
end
end

edit_exchange_point_and_mark(s::MIState) =
edit_exchange_point_and_mark(buffer(s)) && (refresh_line(s); true)

function edit_exchange_point_and_mark(buf::IOBuffer)
m = getmark(buf)
p = position(buf)
m == p && return false
mark(buf)
seek(buf, m)
true
end

char_move_left(s::PromptState) = char_move_left(s.input_buffer)
function char_move_left(buf::IOBuffer)
while position(buf) > 0
Expand Down Expand Up @@ -430,16 +455,23 @@ end

# splice! for IOBuffer: convert from 0-indexed positions, update the size,
# and keep the cursor position stable with the text
# returns the removed portion as a String
function splice_buffer!(buf::IOBuffer, r::UnitRange{<:Integer}, ins::AbstractString = "")
pos = position(buf)
if !isempty(r) && pos in r
if pos in r
seek(buf, first(r))
elseif pos > last(r)
seek(buf, pos - length(r))
end
splice!(buf.data, r + 1, Vector{UInt8}(ins)) # position(), etc, are 0-indexed
if first(r) < buf.mark <= last(r)
unmark(buf)
elseif buf.mark > last(r) && !isempty(r)
buf.mark += sizeof(ins) - length(r)
end
ret = splice!(buf.data, r + 1, Vector{UInt8}(ins)) # position(), etc, are 0-indexed
buf.size = buf.size + sizeof(ins) - length(r)
seek(buf, position(buf) + sizeof(ins))
String(ret)
end

function edit_replace(s, from, to, str)
Expand Down Expand Up @@ -587,7 +619,28 @@ function edit_kill_line(s::MIState)
refresh_line(s)
end

function edit_copy_region(s::MIState)
buf = buffer(s)
if edit_exchange_point_and_mark(buf) # region non-empty
s.kill_buffer = String(buf.data[region(buf)+1])
if REGION_ANIMATION_DURATION[] > 0.0
refresh_line(s)
sleep(REGION_ANIMATION_DURATION[])
end
edit_exchange_point_and_mark(s) # includes refresh_line
end
end

function edit_kill_region(s::MIState)
reg = region(s)
if !isempty(reg)
s.kill_buffer = splice_buffer!(buffer(s), reg)
refresh_line(s)
end
end

edit_transpose(s) = edit_transpose(buffer(s)) && refresh_line(s)

function edit_transpose(buf::IOBuffer)
position(buf) == 0 && return false
eof(buf) && char_move_left(buf)
Expand Down Expand Up @@ -728,20 +781,14 @@ function add_nested_key!(keymap::Dict, key, value; override = false)
i = start(key)
while !done(key, i)
c, i = next(key, i)
if c in keys(keymap)
if done(key, i) && override
# isa(keymap[c], Dict) - In this case we're overriding a prefix of an existing command
keymap[c] = value
break
else
if !isa(keymap[c], Dict)
error("Conflicting definitions for keyseq " * escape_string(key) * " within one keymap")
end
end
elseif done(key, i)
if !override && c in keys(keymap) && (done(key, i) || !isa(keymap[c], Dict))
error("Conflicting definitions for keyseq " * escape_string(key) *
" within one keymap")
end
if done(key, i)
keymap[c] = value
break
else
elseif !(c in keys(keymap) && isa(keymap[c], Dict))
keymap[c] = Dict{Char,Any}()
end
keymap = keymap[c]
Expand Down Expand Up @@ -1443,6 +1490,9 @@ AnyDict(
return :abort
end
end,
# Ctrl-Space
"\0" => (s,o...)->setmark(s),
"^X^X" => (s,o...)->edit_exchange_point_and_mark(s),
"^B" => (s,o...)->edit_move_left(s),
"^F" => (s,o...)->edit_move_right(s),
# Meta B
Expand All @@ -1465,6 +1515,8 @@ AnyDict(
"^U" => (s,o...)->edit_clear(s),
"^K" => (s,o...)->edit_kill_line(s),
"^Y" => (s,o...)->edit_yank(s),
"\ew" => (s,o...)->edit_copy_region(s),
"\eW" => (s,o...)->edit_kill_region(s),
"^A" => (s,o...)->(move_line_start(s); refresh_line(s)),
"^E" => (s,o...)->(move_line_end(s); refresh_line(s)),
# Try to catch all Home/End keys
Expand Down Expand Up @@ -1665,6 +1717,7 @@ end
buffer(s::PromptState) = s.input_buffer
buffer(s::SearchState) = s.query_buffer
buffer(s::PrefixSearchState) = s.response_buffer
buffer(s::IOBuffer) = s

keymap(s::PromptState, prompt::Prompt) = prompt.keymap_dict
keymap_data(s::PromptState, prompt::Prompt) = prompt.keymap_func_data
Expand Down
10 changes: 6 additions & 4 deletions doc/src/manual/interacting-with-julia.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,17 +160,19 @@ to do so).
| Down arrow | Move down one line (or to the next history entry) |
| Page-up | Change to the previous history entry that matches the text before the cursor |
| Page-down | Change to the next history entry that matches the text before the cursor |
| `meta-F` | Move right one word |
| `meta-B` | Move left one word |
| `meta-f` | Move right one word |
| `meta-b` | Move left one word |
| `meta-<` | Change to the first history entry (of the current session if it is before the current position in history) |
| `meta->` | Change to the last history entry |
| **Editing** |   |
| Backspace, `^H` | Delete the previous character |
| Delete, `^D` | Forward delete one character (when buffer has text) |
| meta-Backspace | Delete the previous word |
| `meta-D` | Forward delete the next word |
| `meta-d` | Forward delete the next word |
| `^W` | Delete previous text up to the nearest whitespace |
| `^K` | "Kill" to end of line, placing the text in a buffer |
| `meta-w` | Copy the current region in the kill buffer |
| `meta-W` | "Kill" the current region, placed the text in the kill buffer |
| `^K` | "Kill" to end of line, placing the text in the kill buffer |
| `^Y` | "Yank" insert the text from the kill buffer |
| `^T` | Transpose the characters about the cursor |
| `^Q` | Write a number in REPL and press `^Q` to open editor at corresponding stackframe or method |
Expand Down

0 comments on commit 8b2bef8

Please sign in to comment.