feat(editor): add config for search wrap_around (#1516)

* feat(editor): add config for search wrap_around

Fixes: https://github.com/helix-editor/helix/issues/1489

* Move search settings into separate config

* Disable linter
pull/1651/head
Matouš Dzivjak 3 years ago committed by GitHub
parent 59b5bf3178
commit fdb9a1677b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1459,6 +1459,7 @@ fn split_selection_on_newline(cx: &mut Context) {
doc.set_selection(view.id, selection); doc.set_selection(view.id, selection);
} }
#[allow(clippy::too_many_arguments)]
fn search_impl( fn search_impl(
doc: &mut Document, doc: &mut Document,
view: &mut View, view: &mut View,
@ -1467,6 +1468,7 @@ fn search_impl(
movement: Movement, movement: Movement,
direction: Direction, direction: Direction,
scrolloff: usize, scrolloff: usize,
wrap_around: bool,
) { ) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
@ -1492,16 +1494,22 @@ fn search_impl(
// 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 = match direction { let mut mat = match direction {
Direction::Forward => regex Direction::Forward => regex.find_at(contents, start),
.find_at(contents, start) Direction::Backward => regex.find_iter(&contents[..start]).last(),
.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
if wrap_around && mat.is_none() {
mat = match direction {
Direction::Forward => regex.find(contents),
Direction::Backward => {
offset = start;
regex.find_iter(&contents[start..]).last()
}
}
// TODO: message on wraparound
}
if let Some(mat) = mat { if let Some(mat) = mat {
let start = text.byte_to_char(mat.start() + offset); let start = text.byte_to_char(mat.start() + offset);
let end = text.byte_to_char(mat.end() + offset); let end = text.byte_to_char(mat.end() + offset);
@ -1554,6 +1562,7 @@ fn rsearch(cx: &mut Context) {
fn searcher(cx: &mut Context, direction: Direction) { fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/'); let reg = cx.register.unwrap_or('/');
let scrolloff = cx.editor.config.scrolloff; let scrolloff = cx.editor.config.scrolloff;
let wrap_around = cx.editor.config.search.wrap_around;
let doc = doc!(cx.editor); let doc = doc!(cx.editor);
@ -1587,6 +1596,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
Movement::Move, Movement::Move,
direction, direction,
scrolloff, scrolloff,
wrap_around,
); );
}, },
); );
@ -1601,16 +1611,27 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
if let Some(query) = registers.read('/') { if let Some(query) = registers.read('/') {
let query = query.last().unwrap(); let query = query.last().unwrap();
let contents = doc.text().slice(..).to_string(); let contents = doc.text().slice(..).to_string();
let case_insensitive = if cx.editor.config.smart_case { let search_config = &cx.editor.config.search;
let case_insensitive = if search_config.smart_case {
!query.chars().any(char::is_uppercase) !query.chars().any(char::is_uppercase)
} else { } else {
false false
}; };
let wrap_around = search_config.wrap_around;
if let Ok(regex) = RegexBuilder::new(query) if let Ok(regex) = RegexBuilder::new(query)
.case_insensitive(case_insensitive) .case_insensitive(case_insensitive)
.build() .build()
{ {
search_impl(doc, view, &contents, &regex, movement, direction, scrolloff); search_impl(
doc,
view,
&contents,
&regex,
movement,
direction,
scrolloff,
wrap_around,
);
} 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
@ -1649,7 +1670,7 @@ fn search_selection(cx: &mut Context) {
fn global_search(cx: &mut Context) { fn global_search(cx: &mut Context) {
let (all_matches_sx, all_matches_rx) = let (all_matches_sx, all_matches_rx) =
tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>(); tokio::sync::mpsc::unbounded_channel::<(usize, PathBuf)>();
let smart_case = cx.editor.config.smart_case; let smart_case = cx.editor.config.search.smart_case;
let file_picker_config = cx.editor.config.file_picker.clone(); let file_picker_config = cx.editor.config.file_picker.clone();
let completions = search_completions(cx, None); let completions = search_completions(cx, None);
@ -2707,12 +2728,13 @@ pub mod cmd {
"mouse" => runtime_config.mouse = arg.parse()?, "mouse" => runtime_config.mouse = arg.parse()?,
"line-number" => runtime_config.line_number = arg.parse()?, "line-number" => runtime_config.line_number = arg.parse()?,
"middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
"smart-case" => runtime_config.smart_case = arg.parse()?,
"auto-pairs" => runtime_config.auto_pairs = arg.parse()?, "auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
"auto-completion" => runtime_config.auto_completion = arg.parse()?, "auto-completion" => runtime_config.auto_completion = arg.parse()?,
"completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
"auto-info" => runtime_config.auto_info = arg.parse()?, "auto-info" => runtime_config.auto_info = arg.parse()?,
"true-color" => runtime_config.true_color = arg.parse()?, "true-color" => runtime_config.true_color = arg.parse()?,
"search.smart-case" => runtime_config.search.smart_case = arg.parse()?,
"search.wrap-around" => runtime_config.search.wrap_around = arg.parse()?,
_ => anyhow::bail!("Unknown key `{}`.", args[0]), _ => anyhow::bail!("Unknown key `{}`.", args[0]),
} }

@ -65,7 +65,7 @@ pub fn regex_prompt(
return; return;
} }
let case_insensitive = if cx.editor.config.smart_case { let case_insensitive = if cx.editor.config.search.smart_case {
!input.chars().any(char::is_uppercase) !input.chars().any(char::is_uppercase)
} else { } else {
false false

@ -93,8 +93,6 @@ pub struct Config {
pub line_number: LineNumber, pub line_number: LineNumber,
/// Middle click paste support. Defaults to true. /// Middle click paste support. Defaults to true.
pub middle_click_paste: bool, pub middle_click_paste: bool,
/// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
pub smart_case: bool,
/// Automatic insertion of pairs to parentheses, brackets, etc. Defaults to true. /// Automatic insertion of pairs to parentheses, brackets, etc. Defaults to true.
pub auto_pairs: bool, pub auto_pairs: bool,
/// Automatic auto-completion, automatically pop up without user trigger. Defaults to true. /// Automatic auto-completion, automatically pop up without user trigger. Defaults to true.
@ -110,6 +108,18 @@ pub struct Config {
pub cursor_shape: CursorShapeConfig, pub cursor_shape: CursorShapeConfig,
/// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`. /// Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. Defaults to `false`.
pub true_color: bool, pub true_color: bool,
/// Search configuration.
#[serde(default)]
pub search: SearchConfig,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct SearchConfig {
/// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
pub smart_case: bool,
/// Whether the search should wrap after depleting the matches. Default to true.
pub wrap_around: bool,
} }
// Cursor shape is read and used on every rendered frame and so needs // Cursor shape is read and used on every rendered frame and so needs
@ -202,7 +212,6 @@ impl Default for Config {
}, },
line_number: LineNumber::Absolute, line_number: LineNumber::Absolute,
middle_click_paste: true, middle_click_paste: true,
smart_case: true,
auto_pairs: true, auto_pairs: true,
auto_completion: true, auto_completion: true,
idle_timeout: Duration::from_millis(400), idle_timeout: Duration::from_millis(400),
@ -211,6 +220,16 @@ impl Default for Config {
file_picker: FilePickerConfig::default(), file_picker: FilePickerConfig::default(),
cursor_shape: CursorShapeConfig::default(), cursor_shape: CursorShapeConfig::default(),
true_color: false, true_color: false,
search: SearchConfig::default(),
}
}
}
impl Default for SearchConfig {
fn default() -> Self {
Self {
wrap_around: true,
smart_case: true,
} }
} }
} }

Loading…
Cancel
Save