Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 35 additions & 13 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,7 @@ fn split_selection_on_newline(cx: &mut Context) {
doc.set_selection(view.id, selection);
}

#[allow(clippy::too_many_arguments)]
fn search_impl(
doc: &mut Document,
view: &mut View,
Expand All @@ -1465,6 +1466,7 @@ fn search_impl(
movement: Movement,
direction: Direction,
scrolloff: usize,
wrap_around: bool,
) {
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
Expand All @@ -1490,16 +1492,22 @@ fn search_impl(

// use find_at to find the next match after the cursor, loop around the end
// Careful, `Regex` uses `bytes` as offsets, not character indices!
let mat = match direction {
Direction::Forward => regex
.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()
}),
let mut mat = match direction {
Direction::Forward => regex.find_at(contents, start),
Direction::Backward => 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 {
let start = text.byte_to_char(mat.start() + offset);
let end = text.byte_to_char(mat.end() + offset);
Expand Down Expand Up @@ -1552,6 +1560,7 @@ fn rsearch(cx: &mut Context) {
fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/');
let scrolloff = cx.editor.config.scrolloff;
let wrap_around = cx.editor.config.search.wrap_around;

let doc = doc!(cx.editor);

Expand Down Expand Up @@ -1585,6 +1594,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
Movement::Move,
direction,
scrolloff,
wrap_around,
);
},
);
Expand All @@ -1599,16 +1609,27 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
if let Some(query) = registers.read('/') {
let query = query.last().unwrap();
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)
} else {
false
};
let wrap_around = search_config.wrap_around;
if let Ok(regex) = RegexBuilder::new(query)
.case_insensitive(case_insensitive)
.build()
{
search_impl(doc, view, &contents, &regex, movement, direction, scrolloff);
search_impl(
doc,
view,
&contents,
&regex,
movement,
direction,
scrolloff,
wrap_around,
);
} else {
// get around warning `mutable_borrow_reservation_conflict`
// which will be a hard error in the future
Expand Down Expand Up @@ -1647,7 +1668,7 @@ fn search_selection(cx: &mut Context) {
fn global_search(cx: &mut Context) {
let (all_matches_sx, all_matches_rx) =
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 completions = search_completions(cx, None);
Expand Down Expand Up @@ -2686,12 +2707,13 @@ pub mod cmd {
"mouse" => runtime_config.mouse = arg.parse()?,
"line-number" => runtime_config.line_number = 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-completion" => runtime_config.auto_completion = arg.parse()?,
"completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
"auto-info" => runtime_config.auto_info = 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]),
}

Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub fn regex_prompt(
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)
} else {
false
Expand Down
25 changes: 22 additions & 3 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ pub struct Config {
pub line_number: LineNumber,
/// Middle click paste support. Defaults to true.
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.
pub auto_pairs: bool,
/// Automatic auto-completion, automatically pop up without user trigger. Defaults to true.
Expand All @@ -107,6 +105,18 @@ pub struct Config {
pub file_picker: FilePickerConfig,
/// 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,
/// 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,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -144,14 +154,23 @@ impl Default for Config {
},
line_number: LineNumber::Absolute,
middle_click_paste: true,
smart_case: true,
auto_pairs: true,
auto_completion: true,
idle_timeout: Duration::from_millis(400),
completion_trigger_len: 2,
auto_info: true,
file_picker: FilePickerConfig::default(),
true_color: false,
search: SearchConfig::default(),
}
}
}

impl Default for SearchConfig {
fn default() -> Self {
Self {
wrap_around: true,
smart_case: true,
}
}
}
Expand Down