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
This commit is contained in:
parent
59b5bf3178
commit
fdb9a1677b
3 changed files with 58 additions and 17 deletions
|
@ -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, ®ex, movement, direction, scrolloff);
|
search_impl(
|
||||||
|
doc,
|
||||||
|
view,
|
||||||
|
&contents,
|
||||||
|
®ex,
|
||||||
|
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…
Add table
Reference in a new issue