WIP - Navigate to prev/next search result
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user