Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REPL: implement a kill ring #23377

Merged
merged 6 commits into from
Sep 3, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
REPL: add few "region" operations
* 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 Sep 3, 2017
commit adae8305403c97ee5563a25b5f55ab73616a5a04
79 changes: 65 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(0.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 @@ -287,6 +300,17 @@ 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)
m == position(buf) && 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 @@ -431,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 @@ -597,6 +628,26 @@ 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_chars(s) = edit_transpose_chars(buffer(s)) && refresh_line(s)

function edit_transpose_chars(buf::IOBuffer)
Expand Down Expand Up @@ -784,20 +835,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 @@ -1499,6 +1544,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 @@ -1521,6 +1569,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 @@ -1725,6 +1775,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
12 changes: 8 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,21 @@ 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 |
| `^-Space` | Set the "mark" in the editing region |
| `^X^X` | Exchange the current position with the mark |
| **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 |
| `meta-u` | Change the next word to uppercase |
Expand Down