diff options
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r-- | helix-term/src/commands.rs | 295 |
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, ®ex, 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; } |