Detailed changes
@@ -54,7 +54,7 @@ use ui::{
Tooltip,
};
use util::{maybe, ResultExt};
-use workspace::searchable::SearchableItemHandle;
+use workspace::searchable::{Direction, SearchableItemHandle};
use workspace::{
item::{self, FollowableItem, Item, ItemHandle},
notifications::NotificationId,
@@ -3060,12 +3060,13 @@ impl SearchableItem for ContextEditor {
fn active_match_index(
&mut self,
+ direction: Direction,
matches: &[Self::Match],
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
self.editor.update(cx, |editor, cx| {
- editor.active_match_index(matches, window, cx)
+ editor.active_match_index(direction, matches, window, cx)
})
}
}
@@ -1589,11 +1589,13 @@ impl SearchableItem for Editor {
fn active_match_index(
&mut self,
+ direction: Direction,
matches: &[Range<Anchor>],
_: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
active_match_index(
+ direction,
matches,
&self.selections.newest_anchor().head(),
&self.buffer().read(cx).snapshot(cx),
@@ -1606,6 +1608,7 @@ impl SearchableItem for Editor {
}
pub fn active_match_index(
+ direction: Direction,
ranges: &[Range<Anchor>],
cursor: &Anchor,
buffer: &MultiBufferSnapshot,
@@ -1613,7 +1616,7 @@ pub fn active_match_index(
if ranges.is_empty() {
None
} else {
- match ranges.binary_search_by(|probe| {
+ let r = ranges.binary_search_by(|probe| {
if probe.end.cmp(cursor, buffer).is_lt() {
Ordering::Less
} else if probe.start.cmp(cursor, buffer).is_gt() {
@@ -1621,8 +1624,15 @@ pub fn active_match_index(
} else {
Ordering::Equal
}
- }) {
- Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
+ });
+ match direction {
+ Direction::Prev => match r {
+ Ok(i) => Some(i),
+ Err(i) => Some(i.saturating_sub(1)),
+ },
+ Direction::Next => match r {
+ Ok(i) | Err(i) => Some(cmp::min(i, ranges.len() - 1)),
+ },
}
}
}
@@ -16,7 +16,7 @@ use std::{borrow::Cow, sync::Arc};
use ui::{prelude::*, Button, Checkbox, ContextMenu, Label, PopoverMenu, ToggleState};
use workspace::{
item::{Item, ItemHandle},
- searchable::{SearchEvent, SearchableItem, SearchableItemHandle},
+ searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
SplitDirection, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
};
@@ -1170,12 +1170,14 @@ impl SearchableItem for LspLogView {
}
fn active_match_index(
&mut self,
+ direction: Direction,
matches: &[Self::Match],
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<usize> {
- self.editor
- .update(cx, |e, cx| e.active_match_index(matches, window, cx))
+ self.editor.update(cx, |e, cx| {
+ e.active_match_index(direction, matches, window, cx)
+ })
}
}
@@ -1306,7 +1306,16 @@ impl BufferSearchBar {
done_rx
}
+ fn reverse_direction_if_backwards(&self, direction: Direction) -> Direction {
+ if self.search_options.contains(SearchOptions::BACKWARDS) {
+ direction.opposite()
+ } else {
+ direction
+ }
+ }
+
pub fn update_match_index(&mut self, window: &mut Window, cx: &mut Context<Self>) {
+ let direction = self.reverse_direction_if_backwards(Direction::Next);
let new_index = self
.active_searchable_item
.as_ref()
@@ -1314,7 +1323,7 @@ impl BufferSearchBar {
let matches = self
.searchable_items_with_matches
.get(&searchable_item.downgrade())?;
- searchable_item.active_match_index(matches, window, cx)
+ searchable_item.active_match_index(direction, matches, window, cx)
});
if new_index != self.active_match_index {
self.active_match_index = new_index;
@@ -1240,6 +1240,7 @@ impl ProjectSearchView {
fn update_match_index(&mut self, cx: &mut Context<Self>) {
let results_editor = self.results_editor.read(cx);
let new_index = active_match_index(
+ Direction::Next,
&self.entity.read(cx).match_ranges,
&results_editor.selections.newest_anchor().head(),
&results_editor.buffer().read(cx).snapshot(cx),
@@ -47,6 +47,8 @@ bitflags! {
const CASE_SENSITIVE = 0b010;
const INCLUDE_IGNORED = 0b100;
const REGEX = 0b1000;
+ /// If set, reverse direction when finding the active match
+ const BACKWARDS = 0b10000;
}
}
@@ -41,7 +41,7 @@ use workspace::{
BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams, TabTooltipContent,
},
register_serializable_item,
- searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
+ searchable::{Direction, SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, ToolbarItemLocation, Workspace,
WorkspaceId,
};
@@ -1583,6 +1583,7 @@ impl SearchableItem for TerminalView {
/// Reports back to the search toolbar what the active match should be (the selection)
fn active_match_index(
&mut self,
+ direction: Direction,
matches: &[Self::Match],
_: &mut Window,
cx: &mut Context<Self>,
@@ -1593,19 +1594,36 @@ impl SearchableItem for TerminalView {
let res = if !matches.is_empty() {
if let Some(selection_head) = self.terminal().read(cx).selection_head {
// If selection head is contained in a match. Return that match
- if let Some(ix) = matches
- .iter()
- .enumerate()
- .find(|(_, search_match)| {
- search_match.contains(&selection_head)
- || search_match.start() > &selection_head
- })
- .map(|(ix, _)| ix)
- {
- Some(ix)
- } else {
- // If no selection after selection head, return the last match
- Some(matches.len().saturating_sub(1))
+ match direction {
+ Direction::Prev => {
+ // If no selection before selection head, return the first match
+ Some(
+ matches
+ .iter()
+ .enumerate()
+ .rev()
+ .find(|(_, search_match)| {
+ search_match.contains(&selection_head)
+ || search_match.start() < &selection_head
+ })
+ .map(|(ix, _)| ix)
+ .unwrap_or(0),
+ )
+ }
+ Direction::Next => {
+ // If no selection after selection head, return the last match
+ Some(
+ matches
+ .iter()
+ .enumerate()
+ .find(|(_, search_match)| {
+ search_match.contains(&selection_head)
+ || search_match.start() > &selection_head
+ })
+ .map(|(ix, _)| ix)
+ .unwrap_or(matches.len().saturating_sub(1)),
+ )
+ }
}
} else {
// Matches found but no active selection, return the first last one (closest to cursor)
@@ -154,6 +154,9 @@ impl Vim {
if action.regex {
options |= SearchOptions::REGEX;
}
+ if action.backwards {
+ options |= SearchOptions::BACKWARDS;
+ }
search_bar.set_search_options(options, cx);
let prior_mode = if self.temp_mode {
Mode::Insert
@@ -198,7 +201,7 @@ impl Vim {
.last()
.map_or(true, |range| range.start != new_head);
- if is_different_head && self.search.direction == Direction::Next {
+ if is_different_head {
count = count.saturating_sub(1)
}
self.search.count = 1;
@@ -743,6 +746,12 @@ mod test {
cx.simulate_keystrokes("*");
cx.assert_state("one two Λone", Mode::Normal);
+ // check that a backward search after last match works correctly
+ cx.set_state("aa\naa\nbbΛ", Mode::Normal);
+ cx.simulate_keystrokes("? a a");
+ cx.simulate_keystrokes("enter");
+ cx.assert_state("aa\nΛaa\nbb", Mode::Normal);
+
// check that searching with unable search wrap
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<EditorSettings>(cx, |s| s.search_wrap = Some(false));
@@ -150,6 +150,7 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
) -> Task<Vec<Self::Match>>;
fn active_match_index(
&mut self,
+ direction: Direction,
matches: &[Self::Match],
window: &mut Window,
cx: &mut Context<Self>,
@@ -208,6 +209,7 @@ pub trait SearchableItemHandle: ItemHandle {
) -> Task<AnyVec<dyn Send>>;
fn active_match_index(
&self,
+ direction: Direction,
matches: &AnyVec<dyn Send>,
window: &mut Window,
cx: &mut App,
@@ -315,13 +317,14 @@ impl<T: SearchableItem> SearchableItemHandle for Entity<T> {
}
fn active_match_index(
&self,
+ direction: Direction,
matches: &AnyVec<dyn Send>,
window: &mut Window,
cx: &mut App,
) -> Option<usize> {
let matches = matches.downcast_ref()?;
self.update(cx, |this, cx| {
- this.active_match_index(matches.as_slice(), window, cx)
+ this.active_match_index(direction, matches.as_slice(), window, cx)
})
}