mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
Allow splitting project find and maintain the searches in sync
This commit is contained in:
parent
29e035a70d
commit
88bfe5acb0
@ -30,6 +30,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
|||||||
struct ProjectFind {
|
struct ProjectFind {
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
excerpts: ModelHandle<MultiBuffer>,
|
excerpts: ModelHandle<MultiBuffer>,
|
||||||
|
query: Option<SearchQuery>,
|
||||||
pending_search: Option<Task<Option<()>>>,
|
pending_search: Option<Task<Option<()>>>,
|
||||||
highlighted_ranges: Vec<Range<Anchor>>,
|
highlighted_ranges: Vec<Range<Anchor>>,
|
||||||
}
|
}
|
||||||
@ -55,7 +56,8 @@ impl ProjectFind {
|
|||||||
Self {
|
Self {
|
||||||
project,
|
project,
|
||||||
excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
|
excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
|
||||||
pending_search: None,
|
query: Default::default(),
|
||||||
|
pending_search: Default::default(),
|
||||||
highlighted_ranges: Default::default(),
|
highlighted_ranges: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +65,8 @@ impl ProjectFind {
|
|||||||
fn search(&mut self, query: SearchQuery, cx: &mut ModelContext<Self>) {
|
fn search(&mut self, query: SearchQuery, cx: &mut ModelContext<Self>) {
|
||||||
let search = self
|
let search = self
|
||||||
.project
|
.project
|
||||||
.update(cx, |project, cx| project.search(query, cx));
|
.update(cx, |project, cx| project.search(query.clone(), cx));
|
||||||
|
self.query = Some(query.clone());
|
||||||
self.highlighted_ranges.clear();
|
self.highlighted_ranges.clear();
|
||||||
self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
|
self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
|
||||||
let matches = search.await;
|
let matches = search.await;
|
||||||
@ -106,7 +109,7 @@ impl workspace::Item for ProjectFind {
|
|||||||
) -> Self::View {
|
) -> Self::View {
|
||||||
let settings = workspace.settings();
|
let settings = workspace.settings();
|
||||||
let excerpts = model.read(cx).excerpts.clone();
|
let excerpts = model.read(cx).excerpts.clone();
|
||||||
cx.observe(&model, ProjectFindView::on_model_changed)
|
cx.observe(&model, |this, _, cx| this.model_changed(true, cx))
|
||||||
.detach();
|
.detach();
|
||||||
ProjectFindView {
|
ProjectFindView {
|
||||||
model,
|
model,
|
||||||
@ -236,6 +239,36 @@ impl workspace::ItemView for ProjectFindView {
|
|||||||
) -> Task<anyhow::Result<()>> {
|
) -> Task<anyhow::Result<()>> {
|
||||||
unreachable!("save_as should not have been called")
|
unreachable!("save_as should not have been called")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let query_editor = cx.add_view(|cx| {
|
||||||
|
Editor::single_line(
|
||||||
|
self.settings.clone(),
|
||||||
|
Some(|theme| theme.find.editor.input.clone()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let results_editor = self.results_editor.update(cx, |editor, cx| {
|
||||||
|
cx.add_view(|cx| editor.clone_on_split(cx).unwrap())
|
||||||
|
});
|
||||||
|
cx.observe(&self.model, |this, _, cx| this.model_changed(true, cx))
|
||||||
|
.detach();
|
||||||
|
let mut view = Self {
|
||||||
|
model: self.model.clone(),
|
||||||
|
query_editor,
|
||||||
|
results_editor,
|
||||||
|
case_sensitive: self.case_sensitive,
|
||||||
|
whole_word: self.whole_word,
|
||||||
|
regex: self.regex,
|
||||||
|
query_contains_error: self.query_contains_error,
|
||||||
|
settings: self.settings.clone(),
|
||||||
|
};
|
||||||
|
view.model_changed(false, cx);
|
||||||
|
Some(view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProjectFindView {
|
impl ProjectFindView {
|
||||||
@ -247,7 +280,7 @@ impl ProjectFindView {
|
|||||||
fn search(&mut self, _: &Search, cx: &mut ViewContext<Self>) {
|
fn search(&mut self, _: &Search, cx: &mut ViewContext<Self>) {
|
||||||
let text = self.query_editor.read(cx).text(cx);
|
let text = self.query_editor.read(cx).text(cx);
|
||||||
let query = if self.regex {
|
let query = if self.regex {
|
||||||
match SearchQuery::regex(text, self.case_sensitive, self.whole_word) {
|
match SearchQuery::regex(text, self.whole_word, self.case_sensitive) {
|
||||||
Ok(query) => query,
|
Ok(query) => query,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.query_contains_error = true;
|
self.query_contains_error = true;
|
||||||
@ -256,7 +289,7 @@ impl ProjectFindView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SearchQuery::text(text, self.case_sensitive, self.whole_word)
|
SearchQuery::text(text, self.whole_word, self.case_sensitive)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.model.update(cx, |model, cx| model.search(query, cx));
|
self.model.update(cx, |model, cx| model.search(query, cx));
|
||||||
@ -285,16 +318,36 @@ impl ProjectFindView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_model_changed(&mut self, _: ModelHandle<ProjectFind>, cx: &mut ViewContext<Self>) {
|
fn model_changed(&mut self, reset_selections: bool, cx: &mut ViewContext<Self>) {
|
||||||
let highlighted_ranges = self.model.read(cx).highlighted_ranges.clone();
|
let model = self.model.read(cx);
|
||||||
|
let highlighted_ranges = model.highlighted_ranges.clone();
|
||||||
|
if let Some(query) = model.query.clone() {
|
||||||
|
self.case_sensitive = query.case_sensitive();
|
||||||
|
self.whole_word = query.whole_word();
|
||||||
|
self.regex = query.is_regex();
|
||||||
|
self.query_editor.update(cx, |query_editor, cx| {
|
||||||
|
if query_editor.text(cx) != query.as_str() {
|
||||||
|
query_editor.buffer().update(cx, |query_buffer, cx| {
|
||||||
|
let len = query_buffer.read(cx).len();
|
||||||
|
query_buffer.edit([0..len], query.as_str(), cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if !highlighted_ranges.is_empty() {
|
if !highlighted_ranges.is_empty() {
|
||||||
let theme = &self.settings.borrow().theme.find;
|
let theme = &self.settings.borrow().theme.find;
|
||||||
self.results_editor.update(cx, |editor, cx| {
|
self.results_editor.update(cx, |editor, cx| {
|
||||||
editor.highlight_ranges::<Self>(highlighted_ranges, theme.match_background, cx);
|
editor.highlight_ranges::<Self>(highlighted_ranges, theme.match_background, cx);
|
||||||
|
if reset_selections {
|
||||||
editor.select_ranges([0..0], Some(Autoscroll::Fit), cx);
|
editor.select_ranges([0..0], Some(Autoscroll::Fit), cx);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if self.query_editor.is_focused(cx) {
|
||||||
cx.focus(&self.results_editor);
|
cx.focus(&self.results_editor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,12 +13,16 @@ use std::{
|
|||||||
pub enum SearchQuery {
|
pub enum SearchQuery {
|
||||||
Text {
|
Text {
|
||||||
search: Arc<AhoCorasick<usize>>,
|
search: Arc<AhoCorasick<usize>>,
|
||||||
query: String,
|
query: Arc<str>,
|
||||||
whole_word: bool,
|
whole_word: bool,
|
||||||
|
case_sensitive: bool,
|
||||||
},
|
},
|
||||||
Regex {
|
Regex {
|
||||||
multiline: bool,
|
|
||||||
regex: Regex,
|
regex: Regex,
|
||||||
|
query: Arc<str>,
|
||||||
|
multiline: bool,
|
||||||
|
whole_word: bool,
|
||||||
|
case_sensitive: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,13 +35,15 @@ impl SearchQuery {
|
|||||||
.build(&[&query]);
|
.build(&[&query]);
|
||||||
Self::Text {
|
Self::Text {
|
||||||
search: Arc::new(search),
|
search: Arc::new(search),
|
||||||
query,
|
query: Arc::from(query),
|
||||||
whole_word,
|
whole_word,
|
||||||
|
case_sensitive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn regex(query: impl ToString, whole_word: bool, case_sensitive: bool) -> Result<Self> {
|
pub fn regex(query: impl ToString, whole_word: bool, case_sensitive: bool) -> Result<Self> {
|
||||||
let mut query = query.to_string();
|
let mut query = query.to_string();
|
||||||
|
let initial_query = Arc::from(query.as_str());
|
||||||
if whole_word {
|
if whole_word {
|
||||||
let mut word_query = String::new();
|
let mut word_query = String::new();
|
||||||
word_query.push_str("\\b");
|
word_query.push_str("\\b");
|
||||||
@ -51,7 +57,13 @@ impl SearchQuery {
|
|||||||
.case_insensitive(!case_sensitive)
|
.case_insensitive(!case_sensitive)
|
||||||
.multi_line(multiline)
|
.multi_line(multiline)
|
||||||
.build()?;
|
.build()?;
|
||||||
Ok(Self::Regex { multiline, regex })
|
Ok(Self::Regex {
|
||||||
|
regex,
|
||||||
|
query: initial_query,
|
||||||
|
multiline,
|
||||||
|
whole_word,
|
||||||
|
case_sensitive,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detect<T: Read>(&self, stream: T) -> Result<bool> {
|
pub fn detect<T: Read>(&self, stream: T) -> Result<bool> {
|
||||||
@ -60,7 +72,7 @@ impl SearchQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
SearchQuery::Text { search, .. } => {
|
Self::Text { search, .. } => {
|
||||||
let mat = search.stream_find_iter(stream).next();
|
let mat = search.stream_find_iter(stream).next();
|
||||||
match mat {
|
match mat {
|
||||||
Some(Ok(_)) => Ok(true),
|
Some(Ok(_)) => Ok(true),
|
||||||
@ -68,7 +80,9 @@ impl SearchQuery {
|
|||||||
None => Ok(false),
|
None => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchQuery::Regex { multiline, regex } => {
|
Self::Regex {
|
||||||
|
regex, multiline, ..
|
||||||
|
} => {
|
||||||
let mut reader = BufReader::new(stream);
|
let mut reader = BufReader::new(stream);
|
||||||
if *multiline {
|
if *multiline {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
@ -99,7 +113,7 @@ impl SearchQuery {
|
|||||||
|
|
||||||
let mut matches = Vec::new();
|
let mut matches = Vec::new();
|
||||||
match self {
|
match self {
|
||||||
SearchQuery::Text {
|
Self::Text {
|
||||||
search, whole_word, ..
|
search, whole_word, ..
|
||||||
} => {
|
} => {
|
||||||
for (ix, mat) in search
|
for (ix, mat) in search
|
||||||
@ -123,7 +137,9 @@ impl SearchQuery {
|
|||||||
matches.push(mat.start()..mat.end())
|
matches.push(mat.start()..mat.end())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SearchQuery::Regex { multiline, regex } => {
|
Self::Regex {
|
||||||
|
regex, multiline, ..
|
||||||
|
} => {
|
||||||
if *multiline {
|
if *multiline {
|
||||||
let text = rope.to_string();
|
let text = rope.to_string();
|
||||||
for (ix, mat) in regex.find_iter(&text).enumerate() {
|
for (ix, mat) in regex.find_iter(&text).enumerate() {
|
||||||
@ -161,10 +177,28 @@ impl SearchQuery {
|
|||||||
matches
|
matches
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
SearchQuery::Text { query, .. } => query.as_str(),
|
Self::Text { query, .. } => query.as_ref(),
|
||||||
SearchQuery::Regex { regex, .. } => regex.as_str(),
|
Self::Regex { query, .. } => query.as_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn whole_word(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Text { whole_word, .. } => *whole_word,
|
||||||
|
Self::Regex { whole_word, .. } => *whole_word,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn case_sensitive(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Text { case_sensitive, .. } => *case_sensitive,
|
||||||
|
Self::Regex { case_sensitive, .. } => *case_sensitive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_regex(&self) -> bool {
|
||||||
|
matches!(self, Self::Regex { .. })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user