on master help sign in
Add reverse search functionality (#958)
911b9b3Gygaxis Vainhardt
book/src/keymap.md+18−18
@@ -106,13 +106,13 @@
106
106
107### Search
107### Search
108
108
109−> TODO: The search implementation isn't ideal yet -- we don't support searching in reverse.
110
109
111| Key | Description | Command |
110| Key | Description | Command |
112| ----- | ----------- | ------- |
111| ----- | ----------- | ------- |
113| `/` | Search for regex pattern | `search` |
112| `/` | Search for regex pattern | `search` |
113+| `?` | Search for previous pattern | `rsearch` |
114| `n` | Select next search match | `search_next` |
114| `n` | Select next search match | `search_next` |
115−| `N` | Add next search match to selection | `extend_search_next` |
115+| `N` | Select previous search match | `search_prev` |
116| `*` | Use current selection as the search pattern | `search_selection` |
116| `*` | Use current selection as the search pattern | `search_selection` |
117
117
118### Minor modes
118### Minor modes
@@ -185,16 +185,16 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
185
185
186This layer is similar to vim keybindings as kakoune does not support window.
186This layer is similar to vim keybindings as kakoune does not support window.
187
187
188−| Key | Description | Command |
188+| Key | Description | Command |
189−| ----- | ------------- | ------- |
189+| ----- | ------------- | ------- |
190−| `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
190+| `w`, `Ctrl-w` | Switch to next window | `rotate_view` |
191−| `v`, `Ctrl-v` | Vertical right split | `vsplit` |
191+| `v`, `Ctrl-v` | Vertical right split | `vsplit` |
192−| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` |
192+| `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` |
193−| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` |
193+| `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` |
194−| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` |
194+| `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` |
195−| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` |
195+| `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` |
196−| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` |
196+| `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` |
197−| `q`, `Ctrl-q` | Close current window | `wclose` |
197+| `q`, `Ctrl-q` | Close current window | `wclose` |
198
198
199#### Space mode
199#### Space mode
200
200
@@ -222,12 +222,12 @@ This layer is a kludge of mappings, mostly pickers.
222
222
223Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
223Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
224
224
225−| Key | Description | Command |
225+| Key | Description | Command |
226−| ----- | ----------- | ------- |
226+| ----- | ----------- | ------- |
227−| `[d` | Go to previous diagnostic | `goto_prev_diag` |
227+| `[d` | Go to previous diagnostic | `goto_prev_diag` |
228−| `]d` | Go to next diagnostic | `goto_next_diag` |
228+| `]d` | Go to next diagnostic | `goto_next_diag` |
229−| `[D` | Go to first diagnostic in document | `goto_first_diag` |
229+| `[D` | Go to first diagnostic in document | `goto_first_diag` |
230−| `]D` | Go to last diagnostic in document | `goto_last_diag` |
230+| `]D` | Go to last diagnostic in document | `goto_last_diag` |
231| `[space` | Add newline above | `add_newline_above` |
231| `[space` | Add newline above | `add_newline_above` |
232| `]space` | Add newline below | `add_newline_below` |
232| `]space` | Add newline below | `add_newline_below` |
233
233
helix-term/src/commands.rs+64−22
@@ -217,8 +217,11 @@ impl Command {
217 split_selection, "Split selection into subselections on regex matches",
217 split_selection, "Split selection into subselections on regex matches",
218 split_selection_on_newline, "Split selection on newlines",
218 split_selection_on_newline, "Split selection on newlines",
219 search, "Search for regex pattern",
219 search, "Search for regex pattern",
220+ rsearch, "Reverse search for regex pattern",
220 search_next, "Select next search match",
221 search_next, "Select next search match",
222+ search_prev, "Select previous search match",
221 extend_search_next, "Add next search match to selection",
223 extend_search_next, "Add next search match to selection",
224+ extend_search_prev, "Add previous search match to selection",
222 search_selection, "Use current selection as search pattern",
225 search_selection, "Use current selection as search pattern",
223 global_search, "Global Search in workspace folder",
226 global_search, "Global Search in workspace folder",
224 extend_line, "Select current line, if already selected, extend to next line",
227 extend_line, "Select current line, if already selected, extend to next line",
@@ -1170,38 +1173,62 @@ fn split_selection_on_newline(cx: &mut Context) {
1170 doc.set_selection(view.id, selection);
1173 doc.set_selection(view.id, selection);
1171}
1174}
1172
1175
1173−fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, extend: bool) {
1176+fn search_impl(
1177+ doc: &mut Document,
1178+ view: &mut View,
1179+ contents: &str,
1180+ regex: &Regex,
1181+ movement: Movement,
1182+ direction: Direction,
1183+) {
1174 let text = doc.text().slice(..);
1184 let text = doc.text().slice(..);
1175 let selection = doc.selection(view.id);
1185 let selection = doc.selection(view.id);
1176
1186
1177− // Get the right side of the primary block cursor.
1187+ // Get the right side of the primary block cursor for forward search, or the
1188+ //grapheme before the start of the selection for reverse search.
1189+ let start = match direction {
1178− let start = text.char_to_byte(graphemes::next_grapheme_boundary(
1190+ Direction::Forward => text.char_to_byte(graphemes::next_grapheme_boundary(
1179− text,
1191+ text,
1192+ selection.primary().to(),
1193+ )),
1194+ Direction::Backward => text.char_to_byte(graphemes::prev_grapheme_boundary(
1195+ text,
1180− selection.primary().cursor(text),
1196+ selection.primary().from(),
1181− ));
1197+ )),
1198+ };
1199+
1200+ //A regex::Match returns byte-positions in the str. In the case where we
1201+ //do a reverse search and wraparound to the end, we don't need to search
1202+ //the text before the current cursor position for matches, but by slicing
1203+ //it out, we need to add it back to the position of the selection.
1204+ let mut offset = 0;
1182
1205
1183 // use find_at to find the next match after the cursor, loop around the end
1206 // use find_at to find the next match after the cursor, loop around the end
1184 // Careful, `Regex` uses `bytes` as offsets, not character indices!
1207 // Careful, `Regex` uses `bytes` as offsets, not character indices!
1185− let mat = regex
1208+ let mat = match direction {
1209+ Direction::Forward => regex
1186− .find_at(contents, start)
1210+ .find_at(contents, start)
1187− .or_else(|| regex.find(contents));
1211+ .or_else(|| regex.find(contents)),
1212+ Direction::Backward => regex.find_iter(&contents[..start]).last().or_else(|| {
1213+ offset = start;
1214+ regex.find_iter(&contents[start..]).last()
1215+ }),
1216+ };
1188 // TODO: message on wraparound
1217 // TODO: message on wraparound
1189 if let Some(mat) = mat {
1218 if let Some(mat) = mat {
1190− let start = text.byte_to_char(mat.start());
1219+ let start = text.byte_to_char(mat.start() + offset);
1191− let end = text.byte_to_char(mat.end());
1220+ let end = text.byte_to_char(mat.end() + offset);
1192
1221
1193 if end == 0 {
1222 if end == 0 {
1194 // skip empty matches that don't make sense
1223 // skip empty matches that don't make sense
1195 return;
1224 return;
1196 }
1225 }
1197−
1198− let selection = if extend {
1226+ let selection = match movement {
1199− selection.clone().push(Range::new(start, end))
1227+ Movement::Extend => selection.clone().push(Range::new(start, end)),
1200− } else {
1201− selection
1228+ Movement::Move => selection
1202 .clone()
1229 .clone()
1203 .remove(selection.primary_index())
1230 .remove(selection.primary_index())
1204− .push(Range::new(start, end))
1231+ .push(Range::new(start, end)),
1205 };
1232 };
1206
1233
1207 doc.set_selection(view.id, selection);
1234 doc.set_selection(view.id, selection);
@@ -1220,6 +1247,14 @@ fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1220
1247
1221// TODO: use one function for search vs extend
1248// TODO: use one function for search vs extend
1222fn search(cx: &mut Context) {
1249fn search(cx: &mut Context) {
1250+ searcher(cx, Direction::Forward)
1251+}
1252+
1253+fn rsearch(cx: &mut Context) {
1254+ searcher(cx, Direction::Backward)
1255+}
1256+// TODO: use one function for search vs extend
1257+fn searcher(cx: &mut Context, direction: Direction) {
1223 let reg = cx.register.unwrap_or('/');
1258 let reg = cx.register.unwrap_or('/');
1224 let (_, doc) = current!(cx.editor);
1259 let (_, doc) = current!(cx.editor);
1225
1260
@@ -1245,14 +1280,14 @@ fn search(cx: &mut Context) {
1245 if event != PromptEvent::Update {
1280 if event != PromptEvent::Update {
1246 return;
1281 return;
1247 }
1282 }
1248− search_impl(doc, view, &contents, &regex, false);
1283+ search_impl(doc, view, &contents, &regex, Movement::Move, direction);
1249 },
1284 },
1250 );
1285 );
1251
1286
1252 cx.push_layer(Box::new(prompt));
1287 cx.push_layer(Box::new(prompt));
1253}
1288}
1254
1289
1255−fn search_next_impl(cx: &mut Context, extend: bool) {
1290+fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1256 let (view, doc) = current!(cx.editor);
1291 let (view, doc) = current!(cx.editor);
1257 let registers = &cx.editor.registers;
1292 let registers = &cx.editor.registers;
1258 if let Some(query) = registers.read('/') {
1293 if let Some(query) = registers.read('/') {
@@ -1267,7 +1302,7 @@ fn search_next_impl(cx: &mut Context, extend: bool) {
1267 .case_insensitive(case_insensitive)
1302 .case_insensitive(case_insensitive)
1268 .build()
1303 .build()
1269 {
1304 {
1270− search_impl(doc, view, &contents, &regex, extend);
1305+ search_impl(doc, view, &contents, &regex, movement, direction);
1271 } else {
1306 } else {
1272 // get around warning `mutable_borrow_reservation_conflict`
1307 // get around warning `mutable_borrow_reservation_conflict`
1273 // which will be a hard error in the future
1308 // which will be a hard error in the future
@@ -1279,11 +1314,18 @@ fn search_next_impl(cx: &mut Context, extend: bool) {
1279}
1314}
1280
1315
1281fn search_next(cx: &mut Context) {
1316fn search_next(cx: &mut Context) {
1282− search_next_impl(cx, false);
1317+ search_next_or_prev_impl(cx, Movement::Move, Direction::Forward);
1283}
1318}
1284
1319
1320+fn search_prev(cx: &mut Context) {
1321+ search_next_or_prev_impl(cx, Movement::Move, Direction::Backward);
1322+}
1285fn extend_search_next(cx: &mut Context) {
1323fn extend_search_next(cx: &mut Context) {
1286− search_next_impl(cx, true);
1324+ search_next_or_prev_impl(cx, Movement::Extend, Direction::Forward);
1325+}
1326+
1327+fn extend_search_prev(cx: &mut Context) {
1328+ search_next_or_prev_impl(cx, Movement::Extend, Direction::Backward);
1287}
1329}
1288
1330
1289fn search_selection(cx: &mut Context) {
1331fn search_selection(cx: &mut Context) {
helix-term/src/keymap.rs+8−3
@@ -504,10 +504,9 @@ impl Default for Keymaps {
504 },
504 },
505
505
506 "/" => search,
506 "/" => search,
507− // ? for search_reverse
507+ "?" => rsearch,
508 "n" => search_next,
508 "n" => search_next,
509− "N" => extend_search_next,
509+ "N" => search_prev,
510− // N for search_prev
511 "*" => search_selection,
510 "*" => search_selection,
512
511
513 "u" => undo,
512 "u" => undo,
@@ -633,11 +632,17 @@ impl Default for Keymaps {
633 "B" => extend_prev_long_word_start,
632 "B" => extend_prev_long_word_start,
634 "E" => extend_next_long_word_end,
633 "E" => extend_next_long_word_end,
635
634
635+ "n" => extend_search_next,
636+ "N" => extend_search_prev,
637+
636 "t" => extend_till_char,
638 "t" => extend_till_char,
637 "f" => extend_next_char,
639 "f" => extend_next_char,
638 "T" => extend_till_prev_char,
640 "T" => extend_till_prev_char,
639 "F" => extend_prev_char,
641 "F" => extend_prev_char,
640
642
643+ "n" => extend_search_next,
644+ "N" => extend_search_prev,
645+
641 "home" => extend_to_line_start,
646 "home" => extend_to_line_start,
642 "end" => extend_to_line_end,
647 "end" => extend_to_line_end,
643 "esc" => exit_select_mode,
648 "esc" => exit_select_mode,