- Fix opening GoToLine from Command - Extend test to cover go_to_line behavior too Release Notes: - N/A
Unified · Split
| @@ -2046,8 +2046,10 @@ dependencies = [ · 2045 unmodified lines | |||
| 2046 | "editor2", | 2046 | "editor2", |
| 2047 | "env_logger 0.9.3", | 2047 | "env_logger 0.9.3", |
| 2048 | "fuzzy2", | 2048 | "fuzzy2", |
| 2049 | "go_to_line2", | ||
| 2049 | "gpui2", | 2050 | "gpui2", |
| 2050 | "language2", | 2051 | "language2", |
| 2052 | "menu2", | ||
| 2051 | "picker2", | 2053 | "picker2", |
| 2052 | "project2", | 2054 | "project2", |
| 2053 | "serde", | 2055 | "serde", |
| @@ -28,6 +28,8 @@ gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } · 27 unmodified lines | |||
| 28 | editor = { package = "editor2", path = "../editor2", features = ["test-support"] } | 28 | editor = { package = "editor2", path = "../editor2", features = ["test-support"] } |
| 29 | language = { package="language2", path = "../language2", features = ["test-support"] } | 29 | language = { package="language2", path = "../language2", features = ["test-support"] } |
| 30 | project = { package="project2", path = "../project2", features = ["test-support"] } | 30 | project = { package="project2", path = "../project2", features = ["test-support"] } |
| 31 | menu = { package = "menu2", path = "../menu2" } | ||
| 32 | go_to_line = { package = "go_to_line2", path = "../go_to_line2" } | ||
| 31 | serde_json.workspace = true | 33 | serde_json.workspace = true |
| 32 | workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] } | 34 | workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] } |
| 33 | ctor.workspace = true | 35 | ctor.workspace = true |
| @@ -362,7 +362,9 @@ mod tests { · 361 unmodified lines | |||
| 362 | 362 | ||
| 363 | use super::*; | 363 | use super::*; |
| 364 | use editor::Editor; | 364 | use editor::Editor; |
| 365 | use go_to_line::GoToLine; | ||
| 365 | use gpui::TestAppContext; | 366 | use gpui::TestAppContext; |
| 367 | use language::Point; | ||
| 366 | use project::Project; | 368 | use project::Project; |
| 367 | use workspace::{AppState, Workspace}; | 369 | use workspace::{AppState, Workspace}; |
| 368 | 370 | ||
| @@ -385,7 +387,6 @@ mod tests { · 16 unmodified lines | |||
| 385 | #[gpui::test] | 387 | #[gpui::test] |
| 386 | async fn test_command_palette(cx: &mut TestAppContext) { | 388 | async fn test_command_palette(cx: &mut TestAppContext) { |
| 387 | let app_state = init_test(cx); | 389 | let app_state = init_test(cx); |
| 388 | |||
| 389 | let project = Project::test(app_state.fs.clone(), [], cx).await; | 390 | let project = Project::test(app_state.fs.clone(), [], cx).await; |
| 390 | let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); | 391 | let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); |
| 391 | 392 | ||
| @@ -455,12 +456,46 @@ mod tests { · 63 unmodified lines | |||
| 455 | }); | 456 | }); |
| 456 | } | 457 | } |
| 457 | 458 | ||
| 459 | #[gpui::test] | ||
| 460 | async fn test_go_to_line(cx: &mut TestAppContext) { | ||
| 461 | let app_state = init_test(cx); | ||
| 462 | let project = Project::test(app_state.fs.clone(), [], cx).await; | ||
| 463 | let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); | ||
| 464 | |||
| 465 | cx.simulate_keystrokes("cmd-n"); | ||
| 466 | |||
| 467 | let editor = workspace.update(cx, |workspace, cx| { | ||
| 468 | workspace.active_item_as::<Editor>(cx).unwrap() | ||
| 469 | }); | ||
| 470 | editor.update(cx, |editor, cx| editor.set_text("1\n2\n3\n4\n5\n6\n", cx)); | ||
| 471 | |||
| 472 | cx.simulate_keystrokes("cmd-shift-p"); | ||
| 473 | cx.simulate_input("go to line: Toggle"); | ||
| 474 | cx.simulate_keystrokes("enter"); | ||
| 475 | |||
| 476 | workspace.update(cx, |workspace, cx| { | ||
| 477 | assert!(workspace.active_modal::<GoToLine>(cx).is_some()) | ||
| 478 | }); | ||
| 479 | |||
| 480 | cx.simulate_keystrokes("3 enter"); | ||
| 481 | |||
| 482 | editor.update(cx, |editor, cx| { | ||
| 483 | assert!(editor.focus_handle(cx).is_focused(cx)); | ||
| 484 | assert_eq!( | ||
| 485 | editor.selections.last::<Point>(cx).range().start, | ||
| 486 | Point::new(2, 0) | ||
| 487 | ); | ||
| 488 | }); | ||
| 489 | } | ||
| 490 | |||
| 458 | fn init_test(cx: &mut TestAppContext) -> Arc<AppState> { | 491 | fn init_test(cx: &mut TestAppContext) -> Arc<AppState> { |
| 459 | cx.update(|cx| { | 492 | cx.update(|cx| { |
| 460 | let app_state = AppState::test(cx); | 493 | let app_state = AppState::test(cx); |
| 461 | theme::init(theme::LoadThemes::JustBase, cx); | 494 | theme::init(theme::LoadThemes::JustBase, cx); |
| 462 | language::init(cx); | 495 | language::init(cx); |
| 463 | editor::init(cx); | 496 | editor::init(cx); |
| 497 | menu::init(); | ||
| 498 | go_to_line::init(cx); | ||
| 464 | workspace::init(app_state.clone(), cx); | 499 | workspace::init(app_state.clone(), cx); |
| 465 | init(cx); | 500 | init(cx); |
| 466 | Project::init_settings(cx); | 501 | Project::init_settings(cx); |
| @@ -53,6 +53,7 @@ pub struct FeedbackModal { · 52 unmodified lines | |||
| 53 | email_address_editor: View<Editor>, | 53 | email_address_editor: View<Editor>, |
| 54 | awaiting_submission: bool, | 54 | awaiting_submission: bool, |
| 55 | user_submitted: bool, | 55 | user_submitted: bool, |
| 56 | discarded: bool, | ||
| 56 | character_count: i32, | 57 | character_count: i32, |
| 57 | } | 58 | } |
| 58 | 59 | ||
| @@ -64,21 +65,35 @@ impl FocusableView for FeedbackModal { · 5 unmodified lines | |||
| 64 | impl EventEmitter<DismissEvent> for FeedbackModal {} | 65 | impl EventEmitter<DismissEvent> for FeedbackModal {} |
| 65 | 66 | ||
| 66 | impl ModalView for FeedbackModal { | 67 | impl ModalView for FeedbackModal { |
| 67 | fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> { | 68 | fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> bool { |
| 68 | if self.user_submitted { | 69 | if self.user_submitted { |
| 69 | self.set_user_submitted(false, cx); | 70 | self.set_user_submitted(false, cx); |
| 70 | return cx.spawn(|_, _| async { true }); | 71 | return true; |
| 71 | } | 72 | } |
| 72 | 73 | ||
| 73 | let has_feedback = self.feedback_editor.read(cx).text_option(cx).is_some(); | 74 | if self.discarded { |
| 75 | return true; | ||
| 76 | } | ||
| 74 | 77 | ||
| 78 | let has_feedback = self.feedback_editor.read(cx).text_option(cx).is_some(); | ||
| 75 | if !has_feedback { | 79 | if !has_feedback { |
| 76 | return cx.spawn(|_, _| async { true }); | 80 | return true; |
| 77 | } | 81 | } |
| 78 | 82 | ||
| 79 | let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]); | 83 | let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]); |
| 80 | 84 | ||
| 81 | cx.spawn(|_, _| async { answer.await.ok() == Some(0) }) | 85 | cx.spawn(move |this, mut cx| async move { |
| 86 | if answer.await.ok() == Some(0) { | ||
| 87 | this.update(&mut cx, |this, cx| { | ||
| 88 | this.discarded = true; | ||
| 89 | cx.emit(DismissEvent) | ||
| 90 | }) | ||
| 91 | .log_err(); | ||
| 92 | } | ||
| 93 | }) | ||
| 94 | .detach(); | ||
| 95 | |||
| 96 | false | ||
| 82 | } | 97 | } |
| 83 | } | 98 | } |
| 84 | 99 | ||
| @@ -169,6 +184,7 @@ impl FeedbackModal { · 84 unmodified lines | |||
| 169 | email_address_editor, | 184 | email_address_editor, |
| 170 | awaiting_submission: false, | 185 | awaiting_submission: false, |
| 171 | user_submitted: false, | 186 | user_submitted: false, |
| 187 | discarded: false, | ||
| 172 | character_count: 0, | 188 | character_count: 0, |
| 173 | } | 189 | } |
| 174 | } | 190 | } |
| @@ -92,7 +92,6 @@ impl GoToLine { · 91 unmodified lines | |||
| 92 | cx: &mut ViewContext<Self>, | 92 | cx: &mut ViewContext<Self>, |
| 93 | ) { | 93 | ) { |
| 94 | match event { | 94 | match event { |
| 95 | // todo!() this isn't working... | ||
| 96 | editor::EditorEvent::Blurred => cx.emit(DismissEvent), | 95 | editor::EditorEvent::Blurred => cx.emit(DismissEvent), |
| 97 | editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx), | 96 | editor::EditorEvent::BufferEdited { .. } => self.highlight_current_line(cx), |
| 98 | _ => {} | 97 | _ => {} |
| @@ -1,23 +1,23 @@ | |||
| 1 | use gpui::{ | 1 | use gpui::{ |
| 2 | div, prelude::*, px, AnyView, Div, FocusHandle, ManagedView, Render, Subscription, Task, View, | 2 | div, prelude::*, px, AnyView, DismissEvent, Div, FocusHandle, ManagedView, Render, |
| 3 | ViewContext, WindowContext, | 3 | Subscription, View, ViewContext, WindowContext, |
| 4 | }; | 4 | }; |
| 5 | use ui::{h_stack, v_stack}; | 5 | use ui::{h_stack, v_stack}; |
| 6 | 6 | ||
| 7 | pub trait ModalView: ManagedView { | 7 | pub trait ModalView: ManagedView { |
| 8 | fn dismiss(&mut self, cx: &mut ViewContext<Self>) -> Task<bool> { | 8 | fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> bool { |
| 9 | Task::ready(true) | 9 | true |
| 10 | } | 10 | } |
| 11 | } | 11 | } |
| 12 | 12 | ||
| 13 | trait ModalViewHandle { | 13 | trait ModalViewHandle { |
| 14 | fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool>; | 14 | fn on_before_dismiss(&mut self, cx: &mut WindowContext) -> bool; |
| 15 | fn view(&self) -> AnyView; | 15 | fn view(&self) -> AnyView; |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | impl<V: ModalView> ModalViewHandle for View<V> { | 18 | impl<V: ModalView> ModalViewHandle for View<V> { |
| 19 | fn should_dismiss(&mut self, cx: &mut WindowContext) -> Task<bool> { | 19 | fn on_before_dismiss(&mut self, cx: &mut WindowContext) -> bool { |
| 20 | self.update(cx, |this, cx| this.dismiss(cx)) | 20 | self.update(cx, |this, cx| this.on_before_dismiss(cx)) |
| 21 | } | 21 | } |
| 22 | 22 | ||
| 23 | fn view(&self) -> AnyView { | 23 | fn view(&self) -> AnyView { |
| @@ -48,8 +48,8 @@ impl ModalLayer { · 24 unmodified lines | |||
| 48 | { | 48 | { |
| 49 | if let Some(active_modal) = &self.active_modal { | 49 | if let Some(active_modal) = &self.active_modal { |
| 50 | let is_close = active_modal.modal.view().downcast::<V>().is_ok(); | 50 | let is_close = active_modal.modal.view().downcast::<V>().is_ok(); |
| 51 | self.hide_modal(cx); | 51 | let did_close = self.hide_modal(cx); |
| 52 | if is_close { | 52 | if is_close || !did_close { |
| 53 | return; | 53 | return; |
| 54 | } | 54 | } |
| 55 | } | 55 | } |
| @@ -57,13 +57,15 @@ impl ModalLayer { · 1 unmodified line | |||
| 57 | self.show_modal(new_modal, cx); | 57 | self.show_modal(new_modal, cx); |
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | pub fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>) | 60 | fn show_modal<V>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>) |
| 61 | where | 61 | where |
| 62 | V: ModalView, | 62 | V: ModalView, |
| 63 | { | 63 | { |
| 64 | self.active_modal = Some(ActiveModal { | 64 | self.active_modal = Some(ActiveModal { |
| 65 | modal: Box::new(new_modal.clone()), | 65 | modal: Box::new(new_modal.clone()), |
| 66 | subscription: cx.subscribe(&new_modal, |this, modal, e, cx| this.hide_modal(cx)), | 66 | subscription: cx.subscribe(&new_modal, |this, modal, _: &DismissEvent, cx| { |
| 67 | this.hide_modal(cx); | ||
| 68 | }), | ||
| 67 | previous_focus_handle: cx.focused(), | 69 | previous_focus_handle: cx.focused(), |
| 68 | focus_handle: cx.focus_handle(), | 70 | focus_handle: cx.focus_handle(), |
| 69 | }); | 71 | }); |
| @@ -71,29 +73,25 @@ impl ModalLayer { · 1 unmodified line | |||
| 71 | cx.notify(); | 73 | cx.notify(); |
| 72 | } | 74 | } |
| 73 | 75 | ||
| 74 | pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) { | 76 | fn hide_modal(&mut self, cx: &mut ViewContext<Self>) -> bool { |
| 75 | let Some(active_modal) = self.active_modal.as_mut() else { | 77 | let Some(active_modal) = self.active_modal.as_mut() else { |
| 76 | return; | 78 | return false; |
| 77 | }; | 79 | }; |
| 78 | 80 | ||
| 79 | let dismiss = active_modal.modal.should_dismiss(cx); | 81 | let dismiss = active_modal.modal.on_before_dismiss(cx); |
| 80 | 82 | if !dismiss { | |
| 81 | cx.spawn(|this, mut cx| async move { | 83 | return false; |
| 82 | if dismiss.await { | 84 | } |
| 83 | this.update(&mut cx, |this, cx| { | 85 | |
| 84 | if let Some(active_modal) = this.active_modal.take() { | 86 | if let Some(active_modal) = self.active_modal.take() { |
| 85 | if let Some(previous_focus) = active_modal.previous_focus_handle { | 87 | if let Some(previous_focus) = active_modal.previous_focus_handle { |
| 86 | if active_modal.focus_handle.contains_focused(cx) { | 88 | if active_modal.focus_handle.contains_focused(cx) { |
| 87 | previous_focus.focus(cx); | 89 | previous_focus.focus(cx); |
| 88 | } | 90 | } |
| 89 | } | ||
| 90 | cx.notify(); | ||
| 91 | } | ||
| 92 | }) | ||
| 93 | .ok(); | ||
| 94 | } | 91 | } |
| 95 | }) | 92 | cx.notify(); |
| 96 | .detach(); | 93 | } |
| 94 | true | ||
| 97 | } | 95 | } |
| 98 | 96 | ||
| 99 | pub fn active_modal<V>(&self) -> Option<View<V>> | 97 | pub fn active_modal<V>(&self) -> Option<View<V>> |