diff --git a/NEWS.md b/NEWS.md index c90cb93b30f16..682c9674fd12b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -652,6 +652,8 @@ Library improvements `enumerate(IndexLinear, iterable)` yields linear indices and `enumerate(IndexCartesian, iterable)` yields cartesian indices ([#16378]). + * Jump to first/last history entries in the REPL via "Alt-<" and "Alt->" ([#22829]). + Compiler/Runtime improvements ----------------------------- diff --git a/base/repl/LineEdit.jl b/base/repl/LineEdit.jl index f76af08c8bf9d..a21f12a6bbfeb 100644 --- a/base/repl/LineEdit.jl +++ b/base/repl/LineEdit.jl @@ -588,6 +588,8 @@ end history_prev(::EmptyHistoryProvider) = ("", false) history_next(::EmptyHistoryProvider) = ("", false) +history_first(::EmptyHistoryProvider) = ("", false) +history_last(::EmptyHistoryProvider) = ("", false) history_search(::EmptyHistoryProvider, args...) = false add_history(::EmptyHistoryProvider, s) = nothing add_history(s::PromptState) = add_history(mode(s).hist, s) @@ -1443,7 +1445,9 @@ const history_keymap = AnyDict( # Page Up "\e[5~" => (s,o...)->(history_prev(s, mode(s).hist)), # Page Down - "\e[6~" => (s,o...)->(history_next(s, mode(s).hist)) + "\e[6~" => (s,o...)->(history_next(s, mode(s).hist)), + "\e<" => (s,o...)->(history_first(s, mode(s).hist)), + "\e>" => (s,o...)->(history_last(s, mode(s).hist)), ) const prefix_history_keymap = merge!( diff --git a/base/repl/REPL.jl b/base/repl/REPL.jl index ca6c38afabe6a..3bb8fd98345e3 100644 --- a/base/repl/REPL.jl +++ b/base/repl/REPL.jl @@ -29,6 +29,8 @@ import ..LineEdit: history_next_prefix, history_prev, history_prev_prefix, + history_first, + history_last, history_search, accept_result, terminal @@ -463,9 +465,10 @@ function LineEdit.accept_result(s, p::LineEdit.HistoryPrompt{REPLHistoryProvider end function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider, - save_idx::Int = hist.cur_idx) + num::Int=1, save_idx::Int = hist.cur_idx) + num <= 0 && return history_next(s, hist, -num, save_idx) hist.last_idx = -1 - m = history_move(s, hist, hist.cur_idx-1, save_idx) + m = history_move(s, hist, hist.cur_idx-num, save_idx) if m === :ok LineEdit.move_input_start(s) LineEdit.reset_key_repeats(s) do @@ -473,15 +476,19 @@ function history_prev(s::LineEdit.MIState, hist::REPLHistoryProvider, end LineEdit.refresh_line(s) elseif m === :skip - hist.cur_idx -= 1 - history_prev(s, hist, save_idx) + history_prev(s, hist, num+1, save_idx) else Terminals.beep(LineEdit.terminal(s)) end end function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider, - save_idx::Int = hist.cur_idx) + num::Int=1, save_idx::Int = hist.cur_idx) + if num == 0 + Terminals.beep(LineEdit.terminal(s)) + return + end + num < 0 && return history_prev(s, hist, -num, save_idx) cur_idx = hist.cur_idx max_idx = length(hist.history) + 1 if cur_idx == max_idx && 0 < hist.last_idx @@ -489,18 +496,23 @@ function history_next(s::LineEdit.MIState, hist::REPLHistoryProvider, cur_idx = hist.last_idx hist.last_idx = -1 end - m = history_move(s, hist, cur_idx+1, save_idx) + m = history_move(s, hist, cur_idx+num, save_idx) if m === :ok LineEdit.move_input_end(s) LineEdit.refresh_line(s) elseif m === :skip - hist.cur_idx += 1 - history_next(s, hist, save_idx) + history_next(s, hist, num+1, save_idx) else Terminals.beep(LineEdit.terminal(s)) end end +history_first(s::LineEdit.MIState, hist::REPLHistoryProvider) = + history_prev(s, hist, hist.cur_idx - 1) + +history_last(s::LineEdit.MIState, hist::REPLHistoryProvider) = + history_next(s, hist, length(hist.history) - hist.cur_idx + 1) + function history_move_prefix(s::LineEdit.PrefixSearchState, hist::REPLHistoryProvider, prefix::AbstractString, diff --git a/doc/src/manual/interacting-with-julia.md b/doc/src/manual/interacting-with-julia.md index 27ea6a752be51..515a640dad5d8 100644 --- a/doc/src/manual/interacting-with-julia.md +++ b/doc/src/manual/interacting-with-julia.md @@ -139,38 +139,40 @@ control-key, there are also meta-key bindings. These vary more by platform, but default to using alt- or option- held down with a key to send the meta-key (or can be configured to do so). -| Keybinding | Description | -|:------------------- |:-------------------------------------------------------------------------------- | -| **Program control** |   | -| `^D` | Exit (when buffer is empty) | -| `^C` | Interrupt or cancel | -| `^L` | Clear console screen | -| Return/Enter, `^J` | New line, executing if it is complete | -| meta-Return/Enter | Insert new line without executing it | -| `?` or `;` | Enter help or shell mode (when at start of a line) | -| `^R`, `^S` | Incremental history search, described above | -| **Cursor movement** |   | -| Right arrow, `^F` | Move right one character | -| Left arrow, `^B` | Move left one character | -| Home, `^A` | Move to beginning of line | -| End, `^E` | Move to end of line | -| `^P` | Change to the previous or next history entry | -| `^N` | Change to the next history entry | -| Up arrow | Move up one line (or to the previous history entry) | -| 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 | -| **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 | -| `^W` | Delete previous text up to the nearest whitespace | -| `^K` | "Kill" to end of line, placing the text in a buffer | -| `^Y` | "Yank" insert the text from the kill buffer | -| `^T` | Transpose the characters about the cursor | +| Keybinding | Description | +|:------------------- |:------------------------------------------------------------------------------------------ | +| **Program control** |   | +| `^D` | Exit (when buffer is empty) | +| `^C` | Interrupt or cancel | +| `^L` | Clear console screen | +| Return/Enter, `^J` | New line, executing if it is complete | +| meta-Return/Enter | Insert new line without executing it | +| `?` or `;` | Enter help or shell mode (when at start of a line) | +| `^R`, `^S` | Incremental history search, described above | +| **Cursor movement** |   | +| Right arrow, `^F` | Move right one character | +| Left arrow, `^B` | Move left one character | +| Home, `^A` | Move to beginning of line | +| End, `^E` | Move to end of line | +| `^P` | Change to the previous or next history entry | +| `^N` | Change to the next history entry | +| Up arrow | Move up one line (or to the previous history entry) | +| 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-<` | Change to the first history entry | +| `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 | +| `^W` | Delete previous text up to the nearest whitespace | +| `^K` | "Kill" to end of line, placing the text in a 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 | diff --git a/test/repl.jl b/test/repl.jl index ddf1f1175c11f..8f46e6562c12f 100644 --- a/test/repl.jl +++ b/test/repl.jl @@ -336,6 +336,26 @@ begin @test LineEdit.mode(s) == repl_mode @test buffercontents(LineEdit.buffer(s)) == "wip" @test position(LineEdit.buffer(s)) == 3 + LineEdit.history_next(s, hp) + @test buffercontents(LineEdit.buffer(s)) == "wip" + LineEdit.history_prev(s, hp, 2) + @test LineEdit.mode(s) == shell_mode + @test buffercontents(LineEdit.buffer(s)) == "ls" + LineEdit.history_prev(s, hp, -2) # equivalent to history_next(s, hp, 2) + @test LineEdit.mode(s) == repl_mode + @test buffercontents(LineEdit.buffer(s)) == "2 + 2" + LineEdit.history_next(s, hp, -2) # equivalent to history_prev(s, hp, 2) + @test LineEdit.mode(s) == shell_mode + @test buffercontents(LineEdit.buffer(s)) == "ls" + LineEdit.history_first(s, hp) + @test LineEdit.mode(s) == repl_mode + @test buffercontents(LineEdit.buffer(s)) == "é" + LineEdit.history_next(s, hp, 6) + @test LineEdit.mode(s) == shell_mode + @test buffercontents(LineEdit.buffer(s)) == "ls" + LineEdit.history_last(s, hp) + @test buffercontents(LineEdit.buffer(s)) == "wip" + @test position(LineEdit.buffer(s)) == 3 LineEdit.move_line_start(s) @test position(LineEdit.buffer(s)) == 0