WIP - Navigate to prev/next search result

This commit is contained in:
Max Brunsfeld
2022-01-28 14:00:00 -08:00
parent 7e02d669e1
commit 5c7cea5a3e
3 changed files with 127 additions and 18 deletions
+13 -5
View File
@@ -861,7 +861,7 @@ impl Editor {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;
let newest_selection = self.newest_selection_internal().unwrap().clone();
let newest_selection = self.newest_anchor_selection().unwrap().clone();
let start;
let end;
@@ -3358,10 +3358,10 @@ impl Editor {
&self,
snapshot: &MultiBufferSnapshot,
) -> Selection<D> {
self.resolve_selection(self.newest_selection_internal().unwrap(), snapshot)
self.resolve_selection(self.newest_anchor_selection().unwrap(), snapshot)
}
pub fn newest_selection_internal(&self) -> Option<&Selection<Anchor>> {
pub fn newest_anchor_selection(&self) -> Option<&Selection<Anchor>> {
self.pending_selection
.as_ref()
.map(|s| &s.selection)
@@ -3377,7 +3377,7 @@ impl Editor {
T: ToOffset + ToPoint + Ord + std::marker::Copy + std::fmt::Debug,
{
let buffer = self.buffer.read(cx).snapshot(cx);
let old_cursor_position = self.newest_selection_internal().map(|s| s.head());
let old_cursor_position = self.newest_anchor_selection().map(|s| s.head());
selections.sort_unstable_by_key(|s| s.start);
// Merge overlapping selections.
@@ -3511,6 +3511,7 @@ impl Editor {
buffer.set_active_selections(&self.selections, cx)
});
}
cx.emit(Event::SelectionsChanged);
}
pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
@@ -3751,7 +3752,7 @@ impl Editor {
}
#[cfg(feature = "test-support")]
pub fn highlighted_ranges(
pub fn all_highlighted_ranges(
&mut self,
cx: &mut ViewContext<Self>,
) -> Vec<(Range<DisplayPoint>, Color)> {
@@ -3762,6 +3763,12 @@ impl Editor {
self.highlighted_ranges_in_range(start..end, &snapshot)
}
pub fn highlighted_ranges_for_type<T: 'static>(&self) -> Option<(Color, &[Range<Anchor>])> {
self.highlighted_ranges
.get(&TypeId::of::<T>())
.map(|(color, ranges)| (*color, ranges.as_slice()))
}
pub fn highlighted_ranges_in_range(
&self,
search_range: Range<Anchor>,
@@ -4011,6 +4018,7 @@ pub enum Event {
Dirtied,
Saved,
TitleChanged,
SelectionsChanged,
Closed,
}
+1 -1
View File
@@ -141,7 +141,7 @@ impl ItemView for Editor {
}
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
if let Some(selection) = self.newest_selection_internal() {
if let Some(selection) = self.newest_anchor_selection() {
self.push_to_nav_history(selection.head(), None, cx);
}
}
+113 -12
View File
@@ -9,12 +9,19 @@ use gpui::{
use postage::watch;
use regex::RegexBuilder;
use smol::future::yield_now;
use std::{ops::Range, sync::Arc};
use std::{cmp::Ordering, ops::Range, sync::Arc};
use workspace::{ItemViewHandle, Settings, Toolbar, Workspace};
action!(Deploy);
action!(Cancel);
action!(ToggleMode, SearchMode);
action!(GoToMatch, Direction);
#[derive(Clone, Copy)]
pub enum Direction {
Prev,
Next,
}
#[derive(Clone, Copy)]
pub enum SearchMode {
@@ -31,12 +38,14 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(FindBar::deploy);
cx.add_action(FindBar::cancel);
cx.add_action(FindBar::toggle_mode);
cx.add_action(FindBar::go_to_match);
}
struct FindBar {
settings: watch::Receiver<Settings>,
query_editor: ViewHandle<Editor>,
active_editor: Option<ViewHandle<Editor>>,
active_match_index: Option<usize>,
active_editor_subscription: Option<Subscription>,
highlighted_editors: HashSet<WeakViewHandle<Editor>>,
pending_search: Option<Task<()>>,
@@ -84,6 +93,12 @@ impl View for FindBar {
.with_style(theme.mode_button_group)
.boxed(),
)
.with_child(
Flex::row()
.with_child(self.render_nav_button("<", Direction::Prev, theme, cx))
.with_child(self.render_nav_button(">", Direction::Next, theme, cx))
.boxed(),
)
.contained()
.with_style(theme.container)
.boxed()
@@ -138,6 +153,7 @@ impl FindBar {
query_editor,
active_editor: None,
active_editor_subscription: None,
active_match_index: None,
highlighted_editors: Default::default(),
case_sensitive_mode: false,
whole_word_mode: false,
@@ -166,7 +182,7 @@ impl FindBar {
cx: &mut RenderContext<Self>,
) -> ElementBox {
let is_active = self.is_mode_enabled(mode);
MouseEventHandler::new::<Self, _, _, _>(mode as usize, cx, |state, _| {
MouseEventHandler::new::<Self, _, _, _>((cx.view_id(), mode as usize), cx, |state, _| {
let style = match (is_active, state.hovered) {
(false, false) => &theme.mode_button,
(false, true) => &theme.hovered_mode_button,
@@ -182,6 +198,32 @@ impl FindBar {
.boxed()
}
fn render_nav_button(
&self,
icon: &str,
direction: Direction,
theme: &theme::Find,
cx: &mut RenderContext<Self>,
) -> ElementBox {
MouseEventHandler::new::<Self, _, _, _>(
(cx.view_id(), 10 + direction as usize),
cx,
|state, _| {
let style = if state.hovered {
&theme.hovered_mode_button
} else {
&theme.mode_button
};
Label::new(icon.to_string(), style.text.clone())
.contained()
.with_style(style.container)
.boxed()
},
)
.on_click(move |cx| cx.dispatch_action(GoToMatch(direction)))
.boxed()
}
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
let settings = workspace.settings();
workspace.active_pane().update(cx, |pane, cx| {
@@ -217,6 +259,32 @@ impl FindBar {
cx.notify();
}
fn go_to_match(&mut self, GoToMatch(direction): &GoToMatch, cx: &mut ViewContext<Self>) {
if let Some(mut index) = self.active_match_index {
if let Some(editor) = self.active_editor.as_ref() {
editor.update(cx, |editor, cx| {
if let Some((_, ranges)) = editor.highlighted_ranges_for_type::<Self>() {
match direction {
Direction::Prev => {
if index == 0 {
index = ranges.len() - 1;
} else {
index -= 1;
}
}
Direction::Next => {
index += 1;
if index >= ranges.len() {
index = 0;
}
}
}
}
});
}
}
}
fn on_query_editor_event(
&mut self,
_: ViewHandle<Editor>,
@@ -244,12 +312,14 @@ impl FindBar {
fn on_active_editor_event(
&mut self,
_: ViewHandle<Editor>,
editor: ViewHandle<Editor>,
event: &editor::Event,
cx: &mut ViewContext<Self>,
) {
match event {
editor::Event::Edited => self.update_matches(cx),
editor::Event::SelectionsChanged => self.update_match_index(cx),
_ => {}
}
}
@@ -279,15 +349,16 @@ impl FindBar {
Ok(ranges) => {
if let Some(editor) = cx.read(|cx| editor.upgrade(cx)) {
this.update(&mut cx, |this, cx| {
let theme = &this.settings.borrow().theme.find;
this.highlighted_editors.insert(editor.downgrade());
editor.update(cx, |editor, cx| {
let theme = &this.settings.borrow().theme.find;
editor.highlight_ranges::<Self>(
ranges,
theme.match_background,
cx,
)
});
this.update_match_index(cx);
});
}
}
@@ -302,6 +373,29 @@ impl FindBar {
}
}
}
fn update_match_index(&mut self, cx: &mut ViewContext<Self>) {
self.active_match_index = self.active_match_index(cx);
}
fn active_match_index(&mut self, cx: &mut ViewContext<Self>) -> Option<usize> {
let editor = self.active_editor.as_ref()?;
let editor = editor.read(cx);
let position = editor.newest_anchor_selection()?.head();
let ranges = editor.highlighted_ranges_for_type::<Self>()?.1;
let buffer = editor.buffer().read(cx).read(cx);
match ranges.binary_search_by(|probe| {
if probe.end.cmp(&position, &*buffer).unwrap().is_lt() {
Ordering::Less
} else if probe.start.cmp(&position, &*buffer).unwrap().is_gt() {
Ordering::Greater
} else {
Ordering::Equal
}
}) {
Ok(i) | Err(i) => Some(i),
}
}
}
const YIELD_INTERVAL: usize = 20000;
@@ -445,14 +539,15 @@ mod tests {
find_bar
});
// default: case-insensitive substring search.
// Search for a string that appears with different casing.
// By default, search is case-insensitive.
find_bar.update(&mut cx, |find_bar, cx| {
find_bar.set_query("us", cx);
});
editor.next_notification(&cx).await;
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.highlighted_ranges(cx),
editor.all_highlighted_ranges(cx),
&[
(
DisplayPoint::new(2, 17)..DisplayPoint::new(2, 19),
@@ -466,28 +561,30 @@ mod tests {
);
});
// switch to case sensitive search
// Switch to a case sensitive search.
find_bar.update(&mut cx, |find_bar, cx| {
find_bar.toggle_mode(&ToggleMode(SearchMode::CaseSensitive), cx);
});
editor.next_notification(&cx).await;
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.highlighted_ranges(cx),
editor.all_highlighted_ranges(cx),
&[(
DisplayPoint::new(2, 43)..DisplayPoint::new(2, 45),
Color::red(),
),]
)]
);
});
// Search for a string that appears both as a whole word and
// within other words. By default, all results are found.
find_bar.update(&mut cx, |find_bar, cx| {
find_bar.set_query("or", cx);
});
editor.next_notification(&cx).await;
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.highlighted_ranges(cx),
editor.all_highlighted_ranges(cx),
&[
(
DisplayPoint::new(0, 24)..DisplayPoint::new(0, 26),
@@ -521,14 +618,14 @@ mod tests {
);
});
// switch to whole word search
// Switch to a whole word search.
find_bar.update(&mut cx, |find_bar, cx| {
find_bar.toggle_mode(&ToggleMode(SearchMode::WholeWord), cx);
});
editor.next_notification(&cx).await;
editor.update(&mut cx, |editor, cx| {
assert_eq!(
editor.highlighted_ranges(cx),
editor.all_highlighted_ranges(cx),
&[
(
DisplayPoint::new(0, 41)..DisplayPoint::new(0, 43),
@@ -545,5 +642,9 @@ mod tests {
]
);
});
find_bar.update(&mut cx, |find_bar, cx| {
find_bar.go_to_match(&GoToMatch(Direction::Next), cx);
});
}
}