Use searcher for search

pull/211/head
Blaž Hrastnik 4 years ago
parent e3ff5de6a5
commit ea7aad99b8

@ -44,10 +44,10 @@ pub fn find_nth_prev(text: RopeSlice, ch: char, mut pos: usize, n: usize) -> Opt
Some(pos) Some(pos)
} }
use crate::movement::Direction;
use regex_automata::{dense, DenseDFA, Error as RegexError, DFA}; use regex_automata::{dense, DenseDFA, Error as RegexError, DFA};
use std::ops::Range; use std::ops::Range;
#[derive(Debug)]
pub struct Searcher { pub struct Searcher {
/// Locate end of match searching right. /// Locate end of match searching right.
right_fdfa: DenseDFA<Vec<usize>, usize>, right_fdfa: DenseDFA<Vec<usize>, usize>,

@ -203,7 +203,9 @@ impl Command {
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",
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",
extend_line, "Select current line, if already selected, extend to next line", extend_line, "Select current line, if already selected, extend to next line",
extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)", extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
@ -1018,25 +1020,91 @@ 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) { // -> we always search from after the cursor.head
let text = doc.text().slice(..); // TODO: be able to use selection as search query (*/alt *)
let selection = doc.selection(view.id);
// Get the right side of the primary block cursor. use helix_core::search::Searcher;
let start = text.char_to_byte(graphemes::next_grapheme_boundary(
text, pub fn search(cx: &mut Context) {
selection.primary().cursor(text), let (view, doc) = current!(cx.editor);
));
// TODO: could probably share with select_on_matches?
let view_id = view.id;
let snapshot = doc.selection(view_id).clone();
let prompt = Prompt::new(
"search:".to_string(),
Some('\\'),
|_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
match event {
PromptEvent::Abort => {
let (view, doc) = current!(cx.editor);
doc.set_selection(view.id, snapshot.clone());
}
PromptEvent::Validate => {
// TODO: push_jump to store selection just before jump
}
PromptEvent::Update => {
// skip empty input, TODO: trigger default
if input.is_empty() {
return;
}
match Searcher::new(input) {
Ok(searcher) => {
let (view, doc) = current!(cx.editor);
// revert state to what it was before the last update
// TODO: also revert text
doc.set_selection(view.id, snapshot.clone());
cx.editor.search = Some(searcher);
_search(cx.editor, Direction::Forward, false);
// TODO: only store on enter (accept), not update
// cx.editor.registers.write('\\', vec![input.to_string()]);
}
Err(_err) => (), // TODO: mark command line as error
}
}
}
},
);
cx.push_layer(Box::new(prompt));
}
pub fn _search(editor: &mut Editor, direction: Direction, extend: bool) {
let (view, doc) = current!(editor);
let text = doc.text().clone(); // need to clone or we run into borrowing issues, but it's a cheap clone
let cursor = doc.selection(view.id).primary().cursor(text.slice(..));
let start = text.char_to_byte(cursor);
let mat = if let Some(searcher) = &editor.search {
// 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 match direction {
.find_at(contents, start) Direction::Backward => searcher
.or_else(|| regex.find(contents)); .search_prev(text.slice(..), start)
.or_else(|| searcher.search_prev(text.slice(..), text.len_bytes())),
Direction::Forward => searcher
.search_next(text.slice(..), start)
.or_else(|| searcher.search_next(text.slice(..), 0)),
}
} else {
None
};
// refetch to avoid borrowing problems
let (view, doc) = current!(editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
// 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);
let end = text.byte_to_char(mat.end()); let end = text.byte_to_char(mat.end);
if end == 0 { if end == 0 {
// skip empty matches that don't make sense // skip empty matches that don't make sense
@ -1052,48 +1120,22 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);
align_view(doc, view, Align::Center); align_view(doc, view, Align::Center);
}; };
view.ensure_cursor_in_view(doc);
} }
// TODO: use one function for search vs extend pub fn search_next(cx: &mut Context) {
fn search(cx: &mut Context) { _search(cx.editor, Direction::Forward, false);
let (_, doc) = current!(cx.editor);
// TODO: could probably share with select_on_matches?
// HAXX: sadly we can't avoid allocating a single string for the whole buffer since we can't
// feed chunks into the regex yet
let contents = doc.text().slice(..).to_string();
let prompt = ui::regex_prompt(
cx,
"search:".to_string(),
move |view, doc, registers, regex| {
search_impl(doc, view, &contents, &regex, false);
// TODO: only store on enter (accept), not update
registers.write('\\', vec![regex.as_str().to_string()]);
},
);
cx.push_layer(Box::new(prompt));
} }
fn search_next_impl(cx: &mut Context, extend: bool) { pub fn extend_search_next(cx: &mut Context) {
let (view, doc) = current!(cx.editor); _search(cx.editor, Direction::Forward, true);
let registers = &mut cx.editor.registers;
if let Some(query) = registers.read('\\') {
let query = query.first().unwrap();
let contents = doc.text().slice(..).to_string();
let regex = Regex::new(query).unwrap();
search_impl(doc, view, &contents, &regex, extend);
}
} }
pub fn search_prev(cx: &mut Context) {
fn search_next(cx: &mut Context) { _search(cx.editor, Direction::Backward, false);
search_next_impl(cx, false);
} }
fn extend_search_next(cx: &mut Context) { pub fn extend_search_prev(cx: &mut Context) {
search_next_impl(cx, true); _search(cx.editor, Direction::Backward, true);
} }
fn search_selection(cx: &mut Context) { fn search_selection(cx: &mut Context) {
@ -1102,7 +1144,8 @@ fn search_selection(cx: &mut Context) {
let query = doc.selection(view.id).primary().fragment(contents); let query = doc.selection(view.id).primary().fragment(contents);
let regex = regex::escape(&query); let regex = regex::escape(&query);
cx.editor.registers.write('\\', vec![regex]); cx.editor.registers.write('\\', vec![regex]);
search_next(cx); let msg = format!("register '{}' set to '{}'", '\\', query);
cx.editor.set_status(msg);
} }
fn extend_line(cx: &mut Context) { fn extend_line(cx: &mut Context) {

@ -417,8 +417,7 @@ impl Default for Keymaps {
"/" => search, "/" => search,
// ? for search_reverse // ? for search_reverse
"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,
@ -522,6 +521,9 @@ impl Default for Keymaps {
"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" => goto_line_start, "home" => goto_line_start,
"end" => goto_line_end, "end" => goto_line_end,
"esc" => exit_select_mode, "esc" => exit_select_mode,

@ -697,7 +697,7 @@ impl EditorView {
impl Component for EditorView { impl Component for EditorView {
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
match event { match event {
Event::Resize(width, height) => { Event::Resize(_width, _height) => {
// Ignore this event, we handle resizing just before rendering to screen. // Ignore this event, we handle resizing just before rendering to screen.
// Handling it here but not re-rendering will cause flashing // Handling it here but not re-rendering will cause flashing
EventResult::Consumed(None) EventResult::Consumed(None)

@ -15,6 +15,7 @@ use anyhow::Error;
pub use helix_core::diagnostic::Severity; pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers; pub use helix_core::register::Registers;
use helix_core::search::Searcher;
use helix_core::syntax; use helix_core::syntax;
use helix_core::Position; use helix_core::Position;
@ -31,6 +32,7 @@ pub struct Editor {
pub syn_loader: Arc<syntax::Loader>, pub syn_loader: Arc<syntax::Loader>,
pub theme_loader: Arc<theme::Loader>, pub theme_loader: Arc<theme::Loader>,
pub search: Option<Searcher>,
pub status_msg: Option<(String, Severity)>, pub status_msg: Option<(String, Severity)>,
} }
@ -65,6 +67,7 @@ impl Editor {
theme_loader: themes, theme_loader: themes,
registers: Registers::default(), registers: Registers::default(),
clipboard_provider: get_clipboard_provider(), clipboard_provider: get_clipboard_provider(),
search: None,
status_msg: None, status_msg: None,
} }
} }

Loading…
Cancel
Save