Repository: helix-editor/helix
File: helix-term/src/commands.rs (Rust)

Code before:
    }
}

fn search_next(cx: &mut Context) {
    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) {
    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) {
    search_selection_generic(cx, |selection, contents| {
        regex::escape(&selection.fragment(contents))
    })
}

fn search_selection_detect_word_boundaries(cx: &mut Context) {
    search_selection_generic(cx, |selection, contents| {
        let is_bow = is_bow(selection.from(), contents);
        let is_eow = is_eow(selection.to(), contents);
        format!(
            "{}{}{}",
            if is_bow { "\\b" } else { "" },
            regex::escape(&selection.fragment(contents)),
            if is_eow { "\\b" } else { "" }
        )
    })
}

fn search_selection_generic(cx: &mut Context, mapper: impl Fn(&Range, RopeSlice) -> String) {
    let register = cx.register.unwrap_or('/');
    let (view, doc) = current!(cx.editor);
    let contents = doc.text().slice(..);

    let regex = doc
        .selection(view.id)
        .iter()
        .map(|selection| mapper(selection, contents))
        .collect::<HashSet<_>>() // Collect into hashset to deduplicate identical regexes
        .into_iter()
        .collect::<Vec<_>>()
        .join("|");


Review comment (security):
Instead of passing down a `impl Fn`, let's just pass a bool to a function like:

```rust
search_selection_impl(cx: &mut Context, detect_word_boundaries: bool)
```

and then we can replace the `map` with `regex::escape` with basically the `format!` in this block but use `if detect_word_boundaries && is_bow { "\\b" } else { "" }`.

Code after:
    }
}

fn search_next(cx: &mut Context) {
    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) {
    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) {
    search_selection_impl(cx, false)
}

fn search_selection_detect_word_boundaries(cx: &mut Context) {
    search_selection_impl(cx, true)
}

fn search_selection_impl(cx: &mut Context, detect_word_boundaries: bool) {
    fn is_at_word_start(text: RopeSlice, index: usize) -> bool {
        let ch = text.char(index);
        if index == 0 {
            return char_is_word(ch);
        }
        let prev_ch = text.char(index - 1);

        !char_is_word(prev_ch) && char_is_word(ch)
    }

    fn is_at_word_end(text: RopeSlice, index: usize) -> bool {
        if index == 0 || index == text.len_chars() {
            return false;
        }
        let ch = text.char(index);
        let prev_ch = text.char(index - 1);

        char_is_word(prev_ch) && !char_is_word(ch)
    }

    let register = cx.register.unwrap_or('/');
    let (view, doc) = current!(cx.editor);
    let text = doc.text().slice(..);