|
|
@ -3,6 +3,7 @@ pub(crate) mod lsp;
|
|
|
|
pub(crate) mod typed;
|
|
|
|
pub(crate) mod typed;
|
|
|
|
|
|
|
|
|
|
|
|
pub use dap::*;
|
|
|
|
pub use dap::*;
|
|
|
|
|
|
|
|
use helix_stdx::rope::{self, RopeSliceExt};
|
|
|
|
use helix_vcs::Hunk;
|
|
|
|
use helix_vcs::Hunk;
|
|
|
|
pub use lsp::*;
|
|
|
|
pub use lsp::*;
|
|
|
|
use tui::widgets::Row;
|
|
|
|
use tui::widgets::Row;
|
|
|
@ -19,7 +20,7 @@ use helix_core::{
|
|
|
|
match_brackets,
|
|
|
|
match_brackets,
|
|
|
|
movement::{self, move_vertically_visual, Direction},
|
|
|
|
movement::{self, move_vertically_visual, Direction},
|
|
|
|
object, pos_at_coords,
|
|
|
|
object, pos_at_coords,
|
|
|
|
regex::{self, Regex, RegexBuilder},
|
|
|
|
regex::{self, Regex},
|
|
|
|
search::{self, CharMatcher},
|
|
|
|
search::{self, CharMatcher},
|
|
|
|
selection, shellwords, surround,
|
|
|
|
selection, shellwords, surround,
|
|
|
|
syntax::LanguageServerFeature,
|
|
|
|
syntax::LanguageServerFeature,
|
|
|
@ -1907,11 +1908,7 @@ fn split_selection(cx: &mut Context) {
|
|
|
|
fn split_selection_on_newline(cx: &mut Context) {
|
|
|
|
fn split_selection_on_newline(cx: &mut Context) {
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let text = doc.text().slice(..);
|
|
|
|
let text = doc.text().slice(..);
|
|
|
|
// only compile the regex once
|
|
|
|
let selection = selection::split_on_newline(text, doc.selection(view.id));
|
|
|
|
#[allow(clippy::trivial_regex)]
|
|
|
|
|
|
|
|
static REGEX: Lazy<Regex> =
|
|
|
|
|
|
|
|
Lazy::new(|| Regex::new(r"\r\n|[\n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}]").unwrap());
|
|
|
|
|
|
|
|
let selection = selection::split_on_matches(text, doc.selection(view.id), ®EX);
|
|
|
|
|
|
|
|
doc.set_selection(view.id, selection);
|
|
|
|
doc.set_selection(view.id, selection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -1930,8 +1927,7 @@ fn merge_consecutive_selections(cx: &mut Context) {
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn search_impl(
|
|
|
|
fn search_impl(
|
|
|
|
editor: &mut Editor,
|
|
|
|
editor: &mut Editor,
|
|
|
|
contents: &str,
|
|
|
|
regex: &rope::Regex,
|
|
|
|
regex: &Regex,
|
|
|
|
|
|
|
|
movement: Movement,
|
|
|
|
movement: Movement,
|
|
|
|
direction: Direction,
|
|
|
|
direction: Direction,
|
|
|
|
scrolloff: usize,
|
|
|
|
scrolloff: usize,
|
|
|
@ -1959,23 +1955,20 @@ fn search_impl(
|
|
|
|
// do a reverse search and wraparound to the end, we don't need to search
|
|
|
|
// do a reverse search and wraparound to the end, we don't need to search
|
|
|
|
// the text before the current cursor position for matches, but by slicing
|
|
|
|
// the text before the current cursor position for matches, but by slicing
|
|
|
|
// it out, we need to add it back to the position of the selection.
|
|
|
|
// it out, we need to add it back to the position of the selection.
|
|
|
|
let mut offset = 0;
|
|
|
|
let doc = doc!(editor).text().slice(..);
|
|
|
|
|
|
|
|
|
|
|
|
// 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 mut mat = match direction {
|
|
|
|
let mut mat = match direction {
|
|
|
|
Direction::Forward => regex.find_at(contents, start),
|
|
|
|
Direction::Forward => regex.find(doc.regex_input_at_bytes(start..)),
|
|
|
|
Direction::Backward => regex.find_iter(&contents[..start]).last(),
|
|
|
|
Direction::Backward => regex.find_iter(doc.regex_input_at_bytes(..start)).last(),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if mat.is_none() {
|
|
|
|
if mat.is_none() {
|
|
|
|
if wrap_around {
|
|
|
|
if wrap_around {
|
|
|
|
mat = match direction {
|
|
|
|
mat = match direction {
|
|
|
|
Direction::Forward => regex.find(contents),
|
|
|
|
Direction::Forward => regex.find(doc.regex_input()),
|
|
|
|
Direction::Backward => {
|
|
|
|
Direction::Backward => regex.find_iter(doc.regex_input_at_bytes(start..)).last(),
|
|
|
|
offset = start;
|
|
|
|
|
|
|
|
regex.find_iter(&contents[start..]).last()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if show_warnings {
|
|
|
|
if show_warnings {
|
|
|
@ -1992,8 +1985,8 @@ fn search_impl(
|
|
|
|
let selection = doc.selection(view.id);
|
|
|
|
let selection = doc.selection(view.id);
|
|
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
let end = text.byte_to_char(mat.end() + offset);
|
|
|
|
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
|
|
|
@ -2037,13 +2030,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
|
|
|
|
let scrolloff = config.scrolloff;
|
|
|
|
let scrolloff = config.scrolloff;
|
|
|
|
let wrap_around = config.search.wrap_around;
|
|
|
|
let wrap_around = config.search.wrap_around;
|
|
|
|
|
|
|
|
|
|
|
|
let doc = doc!(cx.editor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: could probably share with select_on_matches?
|
|
|
|
// 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 completions = search_completions(cx, Some(reg));
|
|
|
|
let completions = search_completions(cx, Some(reg));
|
|
|
|
|
|
|
|
|
|
|
|
ui::regex_prompt(
|
|
|
|
ui::regex_prompt(
|
|
|
@ -2065,7 +2052,6 @@ fn searcher(cx: &mut Context, direction: Direction) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
search_impl(
|
|
|
|
search_impl(
|
|
|
|
cx.editor,
|
|
|
|
cx.editor,
|
|
|
|
&contents,
|
|
|
|
|
|
|
|
®ex,
|
|
|
|
®ex,
|
|
|
|
Movement::Move,
|
|
|
|
Movement::Move,
|
|
|
|
direction,
|
|
|
|
direction,
|
|
|
@ -2085,8 +2071,6 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
|
|
|
|
let config = cx.editor.config();
|
|
|
|
let config = cx.editor.config();
|
|
|
|
let scrolloff = config.scrolloff;
|
|
|
|
let scrolloff = config.scrolloff;
|
|
|
|
if let Some(query) = cx.editor.registers.first(register, cx.editor) {
|
|
|
|
if let Some(query) = cx.editor.registers.first(register, cx.editor) {
|
|
|
|
let doc = doc!(cx.editor);
|
|
|
|
|
|
|
|
let contents = doc.text().slice(..).to_string();
|
|
|
|
|
|
|
|
let search_config = &config.search;
|
|
|
|
let search_config = &config.search;
|
|
|
|
let case_insensitive = if search_config.smart_case {
|
|
|
|
let case_insensitive = if search_config.smart_case {
|
|
|
|
!query.chars().any(char::is_uppercase)
|
|
|
|
!query.chars().any(char::is_uppercase)
|
|
|
@ -2094,15 +2078,17 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
|
|
|
|
false
|
|
|
|
false
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let wrap_around = search_config.wrap_around;
|
|
|
|
let wrap_around = search_config.wrap_around;
|
|
|
|
if let Ok(regex) = RegexBuilder::new(&query)
|
|
|
|
if let Ok(regex) = rope::RegexBuilder::new()
|
|
|
|
.case_insensitive(case_insensitive)
|
|
|
|
.syntax(
|
|
|
|
.multi_line(true)
|
|
|
|
rope::Config::new()
|
|
|
|
.build()
|
|
|
|
.case_insensitive(case_insensitive)
|
|
|
|
|
|
|
|
.multi_line(true),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
.build(&query)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
for _ in 0..count {
|
|
|
|
for _ in 0..count {
|
|
|
|
search_impl(
|
|
|
|
search_impl(
|
|
|
|
cx.editor,
|
|
|
|
cx.editor,
|
|
|
|
&contents,
|
|
|
|
|
|
|
|
®ex,
|
|
|
|
®ex,
|
|
|
|
movement,
|
|
|
|
movement,
|
|
|
|
direction,
|
|
|
|
direction,
|
|
|
@ -2239,7 +2225,7 @@ fn global_search(cx: &mut Context) {
|
|
|
|
|
|
|
|
|
|
|
|
let reg = cx.register.unwrap_or('/');
|
|
|
|
let reg = cx.register.unwrap_or('/');
|
|
|
|
let completions = search_completions(cx, Some(reg));
|
|
|
|
let completions = search_completions(cx, Some(reg));
|
|
|
|
ui::regex_prompt(
|
|
|
|
ui::raw_regex_prompt(
|
|
|
|
cx,
|
|
|
|
cx,
|
|
|
|
"global-search:".into(),
|
|
|
|
"global-search:".into(),
|
|
|
|
Some(reg),
|
|
|
|
Some(reg),
|
|
|
@ -2250,7 +2236,7 @@ fn global_search(cx: &mut Context) {
|
|
|
|
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
|
|
|
|
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
|
|
|
|
.collect()
|
|
|
|
.collect()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
move |cx, regex, event| {
|
|
|
|
move |cx, _, input, event| {
|
|
|
|
if event != PromptEvent::Validate {
|
|
|
|
if event != PromptEvent::Validate {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -2265,7 +2251,7 @@ fn global_search(cx: &mut Context) {
|
|
|
|
|
|
|
|
|
|
|
|
if let Ok(matcher) = RegexMatcherBuilder::new()
|
|
|
|
if let Ok(matcher) = RegexMatcherBuilder::new()
|
|
|
|
.case_smart(smart_case)
|
|
|
|
.case_smart(smart_case)
|
|
|
|
.build(regex.as_str())
|
|
|
|
.build(input)
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let search_root = helix_stdx::env::current_working_dir();
|
|
|
|
let search_root = helix_stdx::env::current_working_dir();
|
|
|
|
if !search_root.exists() {
|
|
|
|
if !search_root.exists() {
|
|
|
|