Add reverse search functionality (#958)

* Add reverse search functionality

* Change keybindings for extend to be in select mode, incorporate Movement and Direction enums

* Fix accidental revert of #948 in rebase

* Add reverse search to docs, clean up mismatched whitespace

* Reverse search optimization

* More optimization via github feedback
imgbot
Gygaxis Vainhardt 3 years ago committed by GitHub
parent cfc8285867
commit 911b9b3276
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -106,13 +106,13 @@
### Search ### Search
> TODO: The search implementation isn't ideal yet -- we don't support searching in reverse.
| Key | Description | Command | | Key | Description | Command |
| ----- | ----------- | ------- | | ----- | ----------- | ------- |
| `/` | Search for regex pattern | `search` | | `/` | Search for regex pattern | `search` |
| `?` | Search for previous pattern | `rsearch` |
| `n` | Select next search match | `search_next` | | `n` | Select next search match | `search_next` |
| `N` | Add next search match to selection | `extend_search_next` | | `N` | Select previous search match | `search_prev` |
| `*` | Use current selection as the search pattern | `search_selection` | | `*` | Use current selection as the search pattern | `search_selection` |
### Minor modes ### Minor modes
@ -185,16 +185,16 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
This layer is similar to vim keybindings as kakoune does not support window. This layer is similar to vim keybindings as kakoune does not support window.
| Key | Description | Command | | Key | Description | Command |
| ----- | ------------- | ------- | | ----- | ------------- | ------- |
| `w`, `Ctrl-w` | Switch to next window | `rotate_view` | | `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
| `v`, `Ctrl-v` | Vertical right split | `vsplit` | | `v`, `Ctrl-v` | Vertical right split | `vsplit` |
| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | | `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` |
| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` | | `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` |
| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` | | `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` |
| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` | | `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` |
| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` | | `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` |
| `q`, `Ctrl-q` | Close current window | `wclose` | | `q`, `Ctrl-q` | Close current window | `wclose` |
#### Space mode #### Space mode
@ -222,12 +222,12 @@ This layer is a kludge of mappings, mostly pickers.
Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired). Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
| Key | Description | Command | | Key | Description | Command |
| ----- | ----------- | ------- | | ----- | ----------- | ------- |
| `[d` | Go to previous diagnostic | `goto_prev_diag` | | `[d` | Go to previous diagnostic | `goto_prev_diag` |
| `]d` | Go to next diagnostic | `goto_next_diag` | | `]d` | Go to next diagnostic | `goto_next_diag` |
| `[D` | Go to first diagnostic in document | `goto_first_diag` | | `[D` | Go to first diagnostic in document | `goto_first_diag` |
| `]D` | Go to last diagnostic in document | `goto_last_diag` | | `]D` | Go to last diagnostic in document | `goto_last_diag` |
| `[space` | Add newline above | `add_newline_above` | | `[space` | Add newline above | `add_newline_above` |
| `]space` | Add newline below | `add_newline_below` | | `]space` | Add newline below | `add_newline_below` |

@ -217,8 +217,11 @@ impl Command {
split_selection, "Split selection into subselections on regex matches", split_selection, "Split selection into subselections on regex matches",
split_selection_on_newline, "Split selection on newlines", split_selection_on_newline, "Split selection on newlines",
search, "Search for regex pattern", search, "Search for regex pattern",
rsearch, "Reverse search for regex pattern",
search_next, "Select next search match", search_next, "Select next search match",
search_prev, "Select previous search match",
extend_search_next, "Add next search match to selection", extend_search_next, "Add next search match to selection",
extend_search_prev, "Add previous search match to selection",
search_selection, "Use current selection as search pattern", search_selection, "Use current selection as search pattern",
global_search, "Global Search in workspace folder", global_search, "Global Search in workspace folder",
extend_line, "Select current line, if already selected, extend to next line", extend_line, "Select current line, if already selected, extend to next line",
@ -1170,38 +1173,62 @@ fn split_selection_on_newline(cx: &mut Context) {
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);
} }
fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) { fn search_impl(
doc: &mut Document,
view: &mut View,
contents: &str,
regex: &Regex,
movement: Movement,
direction: Direction,
) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
// Get the right side of the primary block cursor. // Get the right side of the primary block cursor for forward search, or the
let start = text.char_to_byte(graphemes::next_grapheme_boundary( //grapheme before the start of the selection for reverse search.
text, let start = match direction {
selection.primary().cursor(text), Direction::Forward => text.char_to_byte(graphemes::next_grapheme_boundary(
)); text,
selection.primary().to(),
)),
Direction::Backward => text.char_to_byte(graphemes::prev_grapheme_boundary(
text,
selection.primary().from(),
)),
};
//A regex::Match returns byte-positions in the str. In the case where we
//do a reverse search and wraparound to the end, we don't need to search
//the text before the current cursor position for matches, but by slicing
//it out, we need to add it back to the position of the selection.
let mut offset = 0;
// use find_at to find the next match after the cursor, loop around the end // use find_at to find the next match after the cursor, loop around the end
// Careful, `Regex` uses `bytes` as offsets, not character indices! // Careful, `Regex` uses `bytes` as offsets, not character indices!
let mat = regex let mat = match direction {
.find_at(contents, start) Direction::Forward => regex
.or_else(|| regex.find(contents)); .find_at(contents, start)
.or_else(|| regex.find(contents)),
Direction::Backward => regex.find_iter(&contents[..start]).last().or_else(|| {
offset = start;
regex.find_iter(&contents[start..]).last()
}),
};
// TODO: message on wraparound // TODO: message on wraparound
if let Some(mat) = mat { if let Some(mat) = mat {
let start = text.byte_to_char(mat.start()); let start = text.byte_to_char(mat.start() + offset);
let end = text.byte_to_char(mat.end()); let end = text.byte_to_char(mat.end() + offset);
if end == 0 { if end == 0 {
// skip empty matches that don't make sense // skip empty matches that don't make sense
return; return;
} }
let selection = match movement {
let selection = if extend { Movement::Extend => selection.clone().push(Range::new(start, end)),
selection.clone().push(Range::new(start, end)) Movement::Move => selection
} else {
selection
.clone() .clone()
.remove(selection.primary_index()) .remove(selection.primary_index())
.push(Range::new(start, end)) .push(Range::new(start, end)),
}; };
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);
@ -1220,6 +1247,14 @@ fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
// TODO: use one function for search vs extend // TODO: use one function for search vs extend
fn search(cx: &mut Context) { fn search(cx: &mut Context) {
searcher(cx, Direction::Forward)
}
fn rsearch(cx: &mut Context) {
searcher(cx, Direction::Backward)
}
// TODO: use one function for search vs extend
fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/'); let reg = cx.register.unwrap_or('/');
let (_, doc) = current!(cx.editor); let (_, doc) = current!(cx.editor);
@ -1245,14 +1280,14 @@ fn search(cx: &mut Context) {
if event != PromptEvent::Update { if event != PromptEvent::Update {
return; return;
} }
search_impl(doc, view, &contents, &regex, false); search_impl(doc, view, &contents, &regex, Movement::Move, direction);
}, },
); );
cx.push_layer(Box::new(prompt)); cx.push_layer(Box::new(prompt));
} }
fn search_next_impl(cx: &mut Context, extend: bool) { fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let registers = &cx.editor.registers; let registers = &cx.editor.registers;
if let Some(query) = registers.read('/') { if let Some(query) = registers.read('/') {
@ -1267,7 +1302,7 @@ fn search_next_impl(cx: &mut Context, extend: bool) {
.case_insensitive(case_insensitive) .case_insensitive(case_insensitive)
.build() .build()
{ {
search_impl(doc, view, &contents, &regex, extend); search_impl(doc, view, &contents, &regex, movement, direction);
} else { } else {
// get around warning `mutable_borrow_reservation_conflict` // get around warning `mutable_borrow_reservation_conflict`
// which will be a hard error in the future // which will be a hard error in the future
@ -1279,11 +1314,18 @@ fn search_next_impl(cx: &mut Context, extend: bool) {
} }
fn search_next(cx: &mut Context) { fn search_next(cx: &mut Context) {
search_next_impl(cx, false); search_next_or_prev_impl(cx, Movement::Move, Direction::Forward);
} }
fn search_prev(cx: &mut Context) {
search_next_or_prev_impl(cx, Movement::Move, Direction::Backward);
}
fn extend_search_next(cx: &mut Context) { fn extend_search_next(cx: &mut Context) {
search_next_impl(cx, true); search_next_or_prev_impl(cx, Movement::Extend, Direction::Forward);
}
fn extend_search_prev(cx: &mut Context) {
search_next_or_prev_impl(cx, Movement::Extend, Direction::Backward);
} }
fn search_selection(cx: &mut Context) { fn search_selection(cx: &mut Context) {

@ -504,10 +504,9 @@ impl Default for Keymaps {
}, },
"/" => search, "/" => search,
// ? for search_reverse "?" => rsearch,
"n" => search_next, "n" => search_next,
"N" => extend_search_next, "N" => search_prev,
// N for search_prev
"*" => search_selection, "*" => search_selection,
"u" => undo, "u" => undo,
@ -633,11 +632,17 @@ impl Default for Keymaps {
"B" => extend_prev_long_word_start, "B" => extend_prev_long_word_start,
"E" => extend_next_long_word_end, "E" => extend_next_long_word_end,
"n" => extend_search_next,
"N" => extend_search_prev,
"t" => extend_till_char, "t" => extend_till_char,
"f" => extend_next_char, "f" => extend_next_char,
"T" => extend_till_prev_char, "T" => extend_till_prev_char,
"F" => extend_prev_char, "F" => extend_prev_char,
"n" => extend_search_next,
"N" => extend_search_prev,
"home" => extend_to_line_start, "home" => extend_to_line_start,
"end" => extend_to_line_end, "end" => extend_to_line_end,
"esc" => exit_select_mode, "esc" => exit_select_mode,

Loading…
Cancel
Save