aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src/commands.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r--helix-term/src/commands.rs295
1 files changed, 149 insertions, 146 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 1f88079e..31ea7581 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -43,7 +43,6 @@ use helix_view::{
};
use anyhow::{anyhow, bail, ensure, Context as _};
-use fuzzy_matcher::FuzzyMatcher;
use insert::*;
use movement::Movement;
@@ -60,7 +59,7 @@ use crate::{
};
use crate::job::{self, Jobs};
-use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt};
+use futures_util::{stream::FuturesUnordered, TryStreamExt};
use std::{collections::HashMap, fmt, future::Future};
use std::{collections::HashSet, num::NonZeroUsize};
@@ -75,7 +74,6 @@ use serde::de::{self, Deserialize, Deserializer};
use grep_regex::RegexMatcherBuilder;
use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
use ignore::{DirEntry, WalkBuilder, WalkState};
-use tokio_stream::wrappers::UnboundedReceiverStream;
pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
@@ -1715,8 +1713,8 @@ fn select_regex(cx: &mut Context) {
"select:".into(),
Some(reg),
ui::completers::none,
- move |editor, regex, event| {
- let (view, doc) = current!(editor);
+ move |cx, regex, event| {
+ let (view, doc) = current!(cx.editor);
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}
@@ -1737,8 +1735,8 @@ fn split_selection(cx: &mut Context) {
"split:".into(),
Some(reg),
ui::completers::none,
- move |editor, regex, event| {
- let (view, doc) = current!(editor);
+ move |cx, regex, event| {
+ let (view, doc) = current!(cx.editor);
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}
@@ -1902,14 +1900,14 @@ fn searcher(cx: &mut Context, direction: Direction) {
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
.collect()
},
- move |editor, regex, event| {
+ move |cx, regex, event| {
if event == PromptEvent::Validate {
- editor.registers.last_search_register = reg;
+ cx.editor.registers.last_search_register = reg;
} else if event != PromptEvent::Update {
return;
}
search_impl(
- editor,
+ cx.editor,
&contents,
&regex,
Movement::Move,
@@ -2078,13 +2076,11 @@ fn global_search(cx: &mut Context) {
}
}
- let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
let config = cx.editor.config();
let smart_case = config.search.smart_case;
let file_picker_config = config.file_picker.clone();
let reg = cx.register.unwrap_or('/');
-
let completions = search_completions(cx, Some(reg));
ui::regex_prompt(
cx,
@@ -2097,166 +2093,173 @@ fn global_search(cx: &mut Context) {
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
.collect()
},
- move |editor, regex, event| {
+ move |cx, regex, event| {
if event != PromptEvent::Validate {
return;
}
- editor.registers.last_search_register = reg;
+ cx.editor.registers.last_search_register = reg;
- let documents: Vec<_> = editor
+ let current_path = doc_mut!(cx.editor).path().cloned();
+ let documents: Vec<_> = cx
+ .editor
.documents()
- .map(|doc| (doc.path(), doc.text()))
+ .map(|doc| (doc.path().cloned(), doc.text().to_owned()))
.collect();
if let Ok(matcher) = RegexMatcherBuilder::new()
.case_smart(smart_case)
.build(regex.as_str())
{
- let searcher = SearcherBuilder::new()
- .binary_detection(BinaryDetection::quit(b'\x00'))
- .build();
-
let search_root = helix_loader::current_working_dir();
if !search_root.exists() {
- editor.set_error("Current working directory does not exist");
+ cx.editor
+ .set_error("Current working directory does not exist");
return;
}
+ let (picker, injector) = Picker::stream(current_path);
+
let dedup_symlinks = file_picker_config.deduplicate_links;
let absolute_root = search_root
.canonicalize()
.unwrap_or_else(|_| search_root.clone());
-
- WalkBuilder::new(search_root)
- .hidden(file_picker_config.hidden)
- .parents(file_picker_config.parents)
- .ignore(file_picker_config.ignore)
- .follow_links(file_picker_config.follow_symlinks)
- .git_ignore(file_picker_config.git_ignore)
- .git_global(file_picker_config.git_global)
- .git_exclude(file_picker_config.git_exclude)
- .max_depth(file_picker_config.max_depth)
- .filter_entry(move |entry| {
- filter_picker_entry(entry, &absolute_root, dedup_symlinks)
- })
- .build_parallel()
- .run(|| {
- let mut searcher = searcher.clone();
- let matcher = matcher.clone();
- let all_matches_sx = all_matches_sx.clone();
- let documents = &documents;
- Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
- let entry = match entry {
- Ok(entry) => entry,
- Err(_) => return WalkState::Continue,
- };
-
- match entry.file_type() {
- Some(entry) if entry.is_file() => {}
- // skip everything else
- _ => return WalkState::Continue,
- };
-
- let sink = sinks::UTF8(|line_num, _| {
- all_matches_sx
- .send(FileResult::new(entry.path(), line_num as usize - 1))
- .unwrap();
-
- Ok(true)
- });
- let doc = documents.iter().find(|&(doc_path, _)| {
- doc_path.map_or(false, |doc_path| doc_path == entry.path())
- });
-
- let result = if let Some((_, doc)) = doc {
- // there is already a buffer for this file
- // search the buffer instead of the file because it's faster
- // and captures new edits without requireing a save
- if searcher.multi_line_with_matcher(&matcher) {
- // in this case a continous buffer is required
- // convert the rope to a string
- let text = doc.to_string();
- searcher.search_slice(&matcher, text.as_bytes(), sink)
+ let injector_ = injector.clone();
+
+ std::thread::spawn(move || {
+ let searcher = SearcherBuilder::new()
+ .binary_detection(BinaryDetection::quit(b'\x00'))
+ .build();
+ WalkBuilder::new(search_root)
+ .hidden(file_picker_config.hidden)
+ .parents(file_picker_config.parents)
+ .ignore(file_picker_config.ignore)
+ .follow_links(file_picker_config.follow_symlinks)
+ .git_ignore(file_picker_config.git_ignore)
+ .git_global(file_picker_config.git_global)
+ .git_exclude(file_picker_config.git_exclude)
+ .max_depth(file_picker_config.max_depth)
+ .filter_entry(move |entry| {
+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
+ })
+ .build_parallel()
+ .run(|| {
+ let mut searcher = searcher.clone();
+ let matcher = matcher.clone();
+ let injector = injector_.clone();
+ let documents = &documents;
+ Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
+ let entry = match entry {
+ Ok(entry) => entry,
+ Err(_) => return WalkState::Continue,
+ };
+
+ match entry.file_type() {
+ Some(entry) if entry.is_file() => {}
+ // skip everything else
+ _ => return WalkState::Continue,
+ };
+
+ let mut stop = false;
+ let sink = sinks::UTF8(|line_num, _| {
+ stop = injector
+ .push(FileResult::new(entry.path(), line_num as usize - 1))
+ .is_err();
+
+ Ok(!stop)
+ });
+ let doc = documents.iter().find(|&(doc_path, _)| {
+ doc_path
+ .as_ref()
+ .map_or(false, |doc_path| doc_path == entry.path())
+ });
+
+ let result = if let Some((_, doc)) = doc {
+ // there is already a buffer for this file
+ // search the buffer instead of the file because it's faster
+ // and captures new edits without requiring a save
+ if searcher.multi_line_with_matcher(&matcher) {
+ // in this case a continous buffer is required
+ // convert the rope to a string
+ let text = doc.to_string();
+ searcher.search_slice(&matcher, text.as_bytes(), sink)
+ } else {
+ searcher.search_reader(
+ &matcher,
+ RopeReader::new(doc.slice(..)),
+ sink,
+ )
+ }
} else {
- searcher.search_reader(
- &matcher,
- RopeReader::new(doc.slice(..)),
- sink,
- )
+ searcher.search_path(&matcher, entry.path(), sink)
+ };
+
+ if let Err(err) = result {
+ log::error!(
+ "Global search error: {}, {}",
+ entry.path().display(),
+ err
+ );
}
- } else {
- searcher.search_path(&matcher, entry.path(), sink)
- };
-
- if let Err(err) = result {
- log::error!(
- "Global search error: {}, {}",
- entry.path().display(),
- err
- );
- }
- WalkState::Continue
- })
- });
+ if stop {
+ WalkState::Quit
+ } else {
+ WalkState::Continue
+ }
+ })
+ });
+ });
+
+ cx.jobs.callback(async move {
+ let call = move |_: &mut Editor, compositor: &mut Compositor| {
+ let picker = Picker::with_stream(
+ picker,
+ injector,
+ move |cx, FileResult { path, line_num }, action| {
+ let doc = match cx.editor.open(path, action) {
+ Ok(id) => doc_mut!(cx.editor, &id),
+ Err(e) => {
+ cx.editor.set_error(format!(
+ "Failed to open file '{}': {}",
+ path.display(),
+ e
+ ));
+ return;
+ }
+ };
+
+ let line_num = *line_num;
+ let view = view_mut!(cx.editor);
+ let text = doc.text();
+ if line_num >= text.len_lines() {
+ cx.editor.set_error(
+ "The line you jumped to does not exist anymore because the file has changed.",
+ );
+ return;
+ }
+ let start = text.line_to_char(line_num);
+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
+
+ doc.set_selection(view.id, Selection::single(start, end));
+ if action.align_view(view, doc.id()) {
+ align_view(doc, view, Align::Center);
+ }
+ },
+ )
+ .with_preview(
+ |_editor, FileResult { path, line_num }| {
+ Some((path.clone().into(), Some((*line_num, *line_num))))
+ },
+ );
+ compositor.push(Box::new(overlaid(picker)))
+ };
+ Ok(Callback::EditorCompositor(Box::new(call)))
+ })
} else {
// Otherwise do nothing
// log::warn!("Global Search Invalid Pattern")
}
},
);
-
- let current_path = doc_mut!(cx.editor).path().cloned();
-
- let show_picker = async move {
- let all_matches: Vec<FileResult> =
- UnboundedReceiverStream::new(all_matches_rx).collect().await;
- let call: job::Callback = Callback::EditorCompositor(Box::new(
- move |editor: &mut Editor, compositor: &mut Compositor| {
- if all_matches.is_empty() {
- if !editor.is_err() {
- editor.set_status("No matches found");
- }
- return;
- }
-
- let picker = Picker::new(
- all_matches,
- current_path,
- move |cx, FileResult { path, line_num }, action| {
- let doc = match cx.editor.open(path, action) {
- Ok(id) => doc_mut!(cx.editor, &id),
- Err(e) => {
- cx.editor.set_error(format!(
- "Failed to open file '{}': {}",
- path.display(),
- e
- ));
- return;
- }
- };
- let line_num = *line_num;
- let view = view_mut!(cx.editor);
- let text = doc.text();
- if line_num >= text.len_lines() {
- cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
- return;
- }
- let start = text.line_to_char(line_num);
- let end = text.line_to_char((line_num + 1).min(text.len_lines()));
-
- doc.set_selection(view.id, Selection::single(start, end));
- if action.align_view(view, doc.id()){
- align_view(doc, view, Align::Center);
- }
- }).with_preview(|_editor, FileResult { path, line_num }| {
- Some((path.clone().into(), Some((*line_num, *line_num))))
- });
- compositor.push(Box::new(overlaid(picker)));
- },
- ));
- Ok(call)
- };
- cx.jobs.callback(show_picker);
}
enum Extend {
@@ -4310,8 +4313,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
if remove { "remove:" } else { "keep:" }.into(),
Some(reg),
ui::completers::none,
- move |editor, regex, event| {
- let (view, doc) = current!(editor);
+ move |cx, regex, event| {
+ let (view, doc) = current!(cx.editor);
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}