From 67a93501dcac5ab7adf6d65eca07e0030d5cdeb4 Mon Sep 17 00:00:00 2001 From: Pascal Kuthe Date: Mon, 23 Jan 2023 20:26:07 +0100 Subject: [PATCH] search buffer contents during global search --- helix-core/src/lib.rs | 2 ++ helix-core/src/rope_reader.rs | 37 ++++++++++++++++++++++++++ helix-term/src/commands.rs | 50 +++++++++++++++++++++++++---------- helix-view/src/editor.rs | 30 +++++++++++++++++++-- 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 helix-core/src/rope_reader.rs diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index ee174e69d1cd8..405d8f0ff1a3e 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -37,6 +37,7 @@ pub mod unicode { pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option { line.chars().position(|ch| !ch.is_whitespace()) } +mod rope_reader; /// Find project root. /// @@ -83,6 +84,7 @@ pub fn find_root(root: Option<&str>, root_markers: &[String]) -> std::path::Path top_marker.map_or(current_dir, |a| a.to_path_buf()) } +pub use rope_reader::RopeReader; pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice}; // pub use tendril::StrTendril as Tendril; diff --git a/helix-core/src/rope_reader.rs b/helix-core/src/rope_reader.rs new file mode 100644 index 0000000000000..20ed7bac1c603 --- /dev/null +++ b/helix-core/src/rope_reader.rs @@ -0,0 +1,37 @@ +use std::io; + +use ropey::iter::Chunks; +use ropey::RopeSlice; + +pub struct RopeReader<'a> { + current_chunk: &'a [u8], + chunks: Chunks<'a>, +} + +impl<'a> RopeReader<'a> { + pub fn new(rope: RopeSlice<'a>) -> RopeReader<'a> { + RopeReader { + current_chunk: &[], + chunks: rope.chunks(), + } + } +} + +impl io::Read for RopeReader<'_> { + fn read(&mut self, mut buf: &mut [u8]) -> io::Result { + let buf_len = buf.len(); + loop { + let read_bytes = self.current_chunk.read(buf)?; + buf = &mut buf[read_bytes..]; + if buf.is_empty() { + return Ok(buf_len); + } + + if let Some(next_chunk) = self.chunks.next() { + self.current_chunk = next_chunk.as_bytes(); + } else { + return Ok(buf_len - buf.len()); + } + } + } +} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e773a2c789d8f..452a66180f082 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -22,8 +22,8 @@ use helix_core::{ selection, shellwords, surround, textobject, tree_sitter::Node, unicode::width::UnicodeWidthChar, - visual_coords_at_pos, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, - SmallVec, Tendril, Transaction, + visual_coords_at_pos, LineEnding, Position, Range, Rope, RopeGraphemes, RopeReader, RopeSlice, + Selection, SmallVec, Tendril, Transaction, }; use helix_view::{ clipboard::ClipboardType, @@ -1898,11 +1898,13 @@ fn global_search(cx: &mut Context) { .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone()))) .collect() }, - move |_editor, regex, event| { + move |editor, regex, event| { if event != PromptEvent::Validate { return; } + let documents = editor.shared_documents(); + if let Ok(matcher) = RegexMatcherBuilder::new() .case_smart(smart_case) .build(regex.as_str()) @@ -1931,6 +1933,7 @@ fn global_search(cx: &mut Context) { 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| -> WalkState { let entry = match entry { Ok(entry) => entry, @@ -1943,17 +1946,36 @@ fn global_search(cx: &mut Context) { _ => return WalkState::Continue, }; - let result = searcher.search_path( - &matcher, - entry.path(), - sinks::UTF8(|line_num, _| { - all_matches_sx - .send(FileResult::new(entry.path(), line_num as usize - 1)) - .unwrap(); - - Ok(true) - }), - ); + 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.clone().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) + } else { + searcher.search_reader( + &matcher, + RopeReader::new(doc.slice(..)), + sink, + ) + } + } else { + searcher.search_path(&matcher, entry.path(), sink) + }; if let Err(err) = result { log::error!( diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index eef4a3f99e910..fa6408b6733ac 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -18,7 +18,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; use std::{ borrow::Cow, - collections::{BTreeMap, HashMap}, + collections::{btree_map, BTreeMap, HashMap}, io::stdin, num::NonZeroUsize, path::{Path, PathBuf}, @@ -38,12 +38,12 @@ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; -use helix_core::Position; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig}, Change, }; +use helix_core::{Position, Rope}; use helix_dap as dap; use helix_lsp::lsp; @@ -757,6 +757,26 @@ pub struct Breakpoint { use futures_util::stream::{Flatten, Once}; +/// Threadsave (Sync + Send) iteration over document text (and path) +/// This is necessary because the editor and the document contain +/// interior mutability which makes it impossible to send them across threads. +/// However the document text itself (ropes) is perfectly fine to send across +/// threads +#[derive(Clone)] +pub struct SharedDocuments<'a>(btree_map::Values<'a, DocumentId, Document>); + +unsafe impl Sync for SharedDocuments<'_> {} +unsafe impl Send for SharedDocuments<'_> {} + +impl<'a> Iterator for SharedDocuments<'a> { + type Item = (Option<&'a PathBuf>, &'a Rope); + + fn next(&mut self) -> Option { + let doc = self.0.next()?; + Some((doc.path(), doc.text())) + } +} + pub struct Editor { /// Current editing mode. pub mode: Mode, @@ -1421,6 +1441,12 @@ impl Editor { self.documents.values_mut() } + /// A thread save iterator over the text of all documents + #[inline] + pub fn shared_documents(&self) -> SharedDocuments { + SharedDocuments(self.documents.values()) + } + pub fn document_by_path>(&self, path: P) -> Option<&Document> { self.documents() .find(|doc| doc.path().map(|p| p == path.as_ref()).unwrap_or(false))