Compare commits

...

8 Commits

Author SHA1 Message Date
Conrad Irwin
8ce9582df9 Merge branch 'main' into add_wrapscan_support 2024-07-10 13:52:56 -06:00
hans
b47fd3a496 add search wrap test for test_search 2024-07-04 18:59:10 +08:00
hans
140a620494 add test move to next for search wrap 2024-07-04 15:23:02 +08:00
Conrad Irwin
b2554fe94e Use nvim style wrapscan false 2024-07-03 09:58:11 -06:00
hans
22d6917d1b 💄 2024-06-30 19:09:52 +08:00
hans
76df24d57e add setting for search wrap 2024-06-30 19:06:44 +08:00
hans
ce13c243b5 💄 2024-06-25 17:39:01 +08:00
hans
df70d5fadf add simple support for wrapscan 2024-06-25 17:34:50 +08:00
8 changed files with 137 additions and 19 deletions

View File

@@ -262,6 +262,8 @@
// to both the horizontal and vertical delta values while scrolling.
"scroll_sensitivity": 1.0,
"relative_line_numbers": false,
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
"search_wrap": true,
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
//

View File

@@ -25,6 +25,7 @@ pub struct EditorSettings {
pub expand_excerpt_lines: u32,
#[serde(default)]
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
pub search_wrap: bool,
#[serde(default)]
pub jupyter: Jupyter,
}
@@ -228,6 +229,10 @@ pub struct EditorSettingsContent {
///
/// Default: select
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
/// Whether the editor search results will loop
///
/// Default: true
pub search_wrap: Option<bool>,
/// Jupyter REPL settings.
pub jupyter: Option<Jupyter>,

View File

@@ -9,7 +9,7 @@ use any_vec::AnyVec;
use collections::HashMap;
use editor::{
actions::{Tab, TabPrev},
DisplayPoint, Editor, EditorElement, EditorStyle,
DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,
};
use futures::channel::oneshot;
use gpui::{
@@ -777,6 +777,15 @@ impl BufferSearchBar {
.get(&searchable_item.downgrade())
.filter(|matches| !matches.is_empty())
{
// If 'wrapscan' is disabled, searches do not wrap around the end of the file.
if !EditorSettings::get_global(cx).search_wrap {
if (direction == Direction::Next && index + count >= matches.len())
|| (direction == Direction::Prev && index < count)
{
crate::show_no_more_matches(cx);
return;
}
}
let new_match_index = searchable_item
.match_index_for_direction(matches, index, direction, count, cx);

View File

@@ -8,7 +8,8 @@ use editor::{
actions::SelectAll,
items::active_match_index,
scroll::{Autoscroll, Axis},
Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, MAX_TAB_TITLE_LEN,
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer,
MAX_TAB_TITLE_LEN,
};
use gpui::{
actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
@@ -970,6 +971,16 @@ impl ProjectSearchView {
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
if let Some(index) = self.active_match_index {
let match_ranges = self.model.read(cx).match_ranges.clone();
if !EditorSettings::get_global(cx).search_wrap {
if (direction == Direction::Next && index + 1 >= match_ranges.len())
|| (direction == Direction::Prev && index == 0)
{
crate::show_no_more_matches(cx);
return;
}
}
let new_index = self.results_editor.update(cx, |editor, cx| {
editor.match_index_for_direction(&match_ranges, index, direction, 1, cx)
});

View File

@@ -5,6 +5,8 @@ use project::search::SearchQuery;
pub use project_search::ProjectSearchView;
use ui::{prelude::*, Tooltip};
use ui::{ButtonStyle, IconButton};
use workspace::notifications::NotificationId;
use workspace::{Toast, Workspace};
pub mod buffer_search;
pub mod project_search;
@@ -107,3 +109,21 @@ impl SearchOptions {
})
}
}
pub(crate) fn show_no_more_matches(cx: &mut WindowContext) {
cx.defer(|cx| {
struct NotifType();
let notification_id = NotificationId::unique::<NotifType>();
let Some(workspace) = cx.window_handle().downcast::<Workspace>() else {
return;
};
workspace
.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(notification_id.clone(), "No more matches").autohide(),
cx,
);
})
.ok();
});
}

View File

@@ -526,14 +526,15 @@ fn parse_replace_all(query: &str) -> Replacement {
mod test {
use std::time::Duration;
use editor::{display_map::DisplayRow, DisplayPoint};
use indoc::indoc;
use search::BufferSearchBar;
use crate::{
state::Mode,
test::{NeovimBackedTestContext, VimTestContext},
};
use editor::EditorSettings;
use editor::{display_map::DisplayRow, DisplayPoint};
use indoc::indoc;
use search::BufferSearchBar;
use settings::SettingsStore;
#[gpui::test]
async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
@@ -572,6 +573,44 @@ mod test {
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
}
#[gpui::test]
async fn test_move_to_next_with_no_search_wrap(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<EditorSettings>(cx, |s| s.search_wrap = Some(false));
});
cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes("*");
cx.run_until_parked();
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes("*");
cx.run_until_parked();
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes("#");
cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes("3 *");
cx.run_until_parked();
cx.assert_state("ˇhi\nhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes("g *");
cx.run_until_parked();
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
cx.simulate_keystrokes("n");
cx.assert_state("hi\nhigh\nˇhi\n", Mode::Normal);
cx.simulate_keystrokes("g #");
cx.run_until_parked();
cx.assert_state("hi\nˇhigh\nhi\n", Mode::Normal);
}
#[gpui::test]
async fn test_search(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
@@ -649,6 +688,27 @@ mod test {
cx.assert_editor_state("«oneˇ» two one");
cx.simulate_keystrokes("*");
cx.assert_state("one two ˇone", 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));
});
cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
cx.simulate_keystrokes("/ c c enter");
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
// n to go to next/N to go to previous
cx.simulate_keystrokes("n");
cx.assert_state("aa\nbb\ncc\nˇcc\ncc\n", Mode::Normal);
cx.simulate_keystrokes("shift-n");
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
// ?<enter> to go to previous
cx.simulate_keystrokes("? enter");
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
cx.simulate_keystrokes("? enter");
cx.assert_state("aa\nbb\nˇcc\ncc\ncc\n", Mode::Normal);
}
#[gpui::test]

View File

@@ -7,7 +7,7 @@ use gpui::{
};
use language::DiagnosticSeverity;
use std::{any::TypeId, ops::DerefMut};
use std::{any::TypeId, ops::DerefMut, time::Duration};
use ui::{prelude::*, Tooltip};
use util::ResultExt;
@@ -174,7 +174,7 @@ impl Workspace {
pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
self.dismiss_notification(&toast.id, cx);
self.show_notification(toast.id, cx, |cx| {
self.show_notification(toast.id.clone(), cx, |cx| {
cx.new_view(|_cx| match toast.on_click.as_ref() {
Some((click_msg, on_click)) => {
let on_click = on_click.clone();
@@ -184,7 +184,20 @@ impl Workspace {
}
None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
})
})
});
if toast.autohide {
cx.spawn(|workspace, mut cx| async move {
cx.background_executor()
.timer(Duration::from_millis(5000))
.await;
workspace
.update(&mut cx, |workspace, cx| {
workspace.dismiss_toast(&toast.id, cx)
})
.ok();
})
.detach();
}
}
pub fn dismiss_toast(&mut self, id: &NotificationId, cx: &mut ViewContext<Self>) {

View File

@@ -218,9 +218,11 @@ impl_actions!(
]
);
#[derive(Clone)]
pub struct Toast {
id: NotificationId,
msg: Cow<'static, str>,
autohide: bool,
on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
}
@@ -230,6 +232,7 @@ impl Toast {
id,
msg: msg.into(),
on_click: None,
autohide: false,
}
}
@@ -241,6 +244,11 @@ impl Toast {
self.on_click = Some((message.into(), Arc::new(on_click)));
self
}
pub fn autohide(mut self) -> Self {
self.autohide = true;
self
}
}
impl PartialEq for Toast {
@@ -251,16 +259,6 @@ impl PartialEq for Toast {
}
}
impl Clone for Toast {
fn clone(&self) -> Self {
Toast {
id: self.id.clone(),
msg: self.msg.clone(),
on_click: self.on_click.clone(),
}
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
pub struct OpenTerminal {
pub working_directory: PathBuf,