1#![allow(unused_variables, unused_mut)]
2//todo!()
3
4mod assets;
5pub mod languages;
6mod only_instance;
7mod open_listener;
8
9pub use assets::*;
10use breadcrumbs::Breadcrumbs;
11use collections::VecDeque;
12use editor::{Editor, MultiBuffer};
13use gpui::{
14 actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View,
15 ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
16};
17pub use only_instance::*;
18pub use open_listener::*;
19
20use anyhow::{anyhow, Context as _};
21use project_panel::ProjectPanel;
22use quick_action_bar::QuickActionBar;
23use settings::{initial_local_settings_content, Settings};
24use std::{borrow::Cow, ops::Deref, sync::Arc};
25use terminal_view::terminal_panel::TerminalPanel;
26use util::{
27 asset_str,
28 channel::{AppCommitSha, ReleaseChannel},
29 paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
30 ResultExt,
31};
32use uuid::Uuid;
33use workspace::Pane;
34use workspace::{
35 create_and_open_local_file, dock::PanelHandle,
36 notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile,
37 NewWindow, Workspace, WorkspaceSettings,
38};
39use zed_actions::{OpenBrowser, OpenZedURL};
40
41actions!(
42 About,
43 DebugElements,
44 DecreaseBufferFontSize,
45 Hide,
46 HideOthers,
47 IncreaseBufferFontSize,
48 Minimize,
49 OpenDefaultKeymap,
50 OpenDefaultSettings,
51 OpenKeymap,
52 OpenLicenses,
53 OpenLocalSettings,
54 OpenLog,
55 OpenSettings,
56 OpenTelemetryLog,
57 Quit,
58 ResetBufferFontSize,
59 ResetDatabase,
60 ShowAll,
61 ToggleFullScreen,
62 Zoom,
63);
64
65pub fn build_window_options(
66 bounds: Option<WindowBounds>,
67 display_uuid: Option<Uuid>,
68 cx: &mut AppContext,
69) -> WindowOptions {
70 let bounds = bounds.unwrap_or(WindowBounds::Maximized);
71 let display = display_uuid.and_then(|uuid| {
72 cx.displays()
73 .into_iter()
74 .find(|display| display.uuid().ok() == Some(uuid))
75 });
76
77 WindowOptions {
78 bounds,
79 titlebar: Some(TitlebarOptions {
80 title: None,
81 appears_transparent: true,
82 traffic_light_position: Some(point(px(8.), px(8.))),
83 }),
84 center: false,
85 focus: false,
86 show: false,
87 kind: WindowKind::Normal,
88 is_movable: true,
89 display_id: display.map(|display| display.id()),
90 }
91}
92
93pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
94 cx.observe_new_views(move |workspace: &mut Workspace, cx| {
95 let workspace_handle = cx.view().clone();
96 let center_pane = workspace.active_pane().clone();
97 initialize_pane(workspace, ¢er_pane, cx);
98 cx.subscribe(&workspace_handle, {
99 move |workspace, _, event, cx| {
100 if let workspace::Event::PaneAdded(pane) = event {
101 initialize_pane(workspace, pane, cx);
102 }
103 }
104 })
105 .detach();
106
107 // cx.emit(workspace2::Event::PaneAdded(
108 // workspace.active_pane().clone(),
109 // ));
110
111 // let collab_titlebar_item =
112 // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
113 // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
114
115 let copilot =
116 cx.build_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
117 let diagnostic_summary =
118 cx.build_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
119 let activity_indicator =
120 activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
121 let active_buffer_language =
122 cx.build_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
123 // let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
124 // let feedback_button = cx.add_view(|_| {
125 // feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
126 // });
127 let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
128 workspace.status_bar().update(cx, |status_bar, cx| {
129 status_bar.add_left_item(diagnostic_summary, cx);
130 status_bar.add_left_item(activity_indicator, cx);
131
132 // status_bar.add_right_item(feedback_button, cx);
133 status_bar.add_right_item(copilot, cx);
134 status_bar.add_right_item(active_buffer_language, cx);
135 // status_bar.add_right_item(vim_mode_indicator, cx);
136 status_bar.add_right_item(cursor_position, cx);
137 });
138
139 auto_update::notify_of_any_new_update(cx);
140
141 // vim::observe_keystrokes(cx);
142
143 let handle = cx.view().downgrade();
144 cx.on_window_should_close(move |cx| {
145 handle
146 .update(cx, |workspace, cx| {
147 workspace.close_window(&Default::default(), cx);
148 false
149 })
150 .unwrap_or(true)
151 });
152
153 cx.spawn(|workspace_handle, mut cx| async move {
154 let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
155 let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
156 // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
157 let channels_panel =
158 collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
159 // let chat_panel =
160 // collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
161 // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
162 // workspace_handle.clone(),
163 // cx.clone(),
164 // );
165 let (
166 project_panel,
167 terminal_panel,
168 // assistant_panel,
169 channels_panel,
170 // chat_panel,
171 // notification_panel,
172 ) = futures::try_join!(
173 project_panel,
174 terminal_panel,
175 // assistant_panel,
176 channels_panel,
177 // chat_panel,
178 // notification_panel,
179 )?;
180
181 workspace_handle.update(&mut cx, |workspace, cx| {
182 let project_panel_position = project_panel.position(cx);
183 workspace.add_panel(project_panel, cx);
184 workspace.add_panel(terminal_panel, cx);
185 // workspace.add_panel(assistant_panel, cx);
186 workspace.add_panel(channels_panel, cx);
187 // workspace.add_panel(chat_panel, cx);
188 // workspace.add_panel(notification_panel, cx);
189
190 // if !was_deserialized
191 // && workspace
192 // .project()
193 // .read(cx)
194 // .visible_worktrees(cx)
195 // .any(|tree| {
196 // tree.read(cx)
197 // .root_entry()
198 // .map_or(false, |entry| entry.is_dir())
199 // })
200 // {
201 // workspace.toggle_dock(project_panel_position, cx);
202 // }
203 // cx.focus_self();
204 })
205 })
206 .detach();
207
208 workspace
209 .register_action(about)
210 .register_action(|_, _: &Hide, cx| {
211 cx.hide();
212 })
213 .register_action(|_, _: &HideOthers, cx| {
214 cx.hide_other_apps();
215 })
216 .register_action(|_, _: &ShowAll, cx| {
217 cx.unhide_other_apps();
218 })
219 .register_action(|_, _: &Minimize, cx| {
220 cx.minimize_window();
221 })
222 .register_action(|_, _: &Zoom, cx| {
223 cx.zoom_window();
224 })
225 .register_action(|_, _: &ToggleFullScreen, cx| {
226 cx.toggle_full_screen();
227 })
228 .register_action(quit)
229 .register_action(|_, action: &OpenZedURL, cx| {
230 cx.global::<Arc<OpenListener>>()
231 .open_urls(&[action.url.clone()])
232 })
233 .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url))
234 //todo!(buffer font size)
235 // cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
236 // theme::adjust_font_size(cx, |size| *size += 1.0)
237 // });
238 // cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
239 // theme::adjust_font_size(cx, |size| *size -= 1.0)
240 // });
241 // cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx));
242 .register_action(|_, _: &install_cli::Install, cx| {
243 cx.spawn(|_, cx| async move {
244 install_cli::install_cli(cx.deref())
245 .await
246 .context("error creating CLI symlink")
247 })
248 .detach_and_log_err(cx);
249 })
250 .register_action(|workspace, _: &OpenLog, cx| {
251 open_log_file(workspace, cx);
252 })
253 .register_action(|workspace, _: &OpenLicenses, cx| {
254 open_bundled_file(
255 workspace,
256 asset_str::<Assets>("licenses.md"),
257 "Open Source License Attribution",
258 "Markdown",
259 cx,
260 );
261 })
262 .register_action(
263 move |workspace: &mut Workspace,
264 _: &OpenTelemetryLog,
265 cx: &mut ViewContext<Workspace>| {
266 open_telemetry_log_file(workspace, cx);
267 },
268 )
269 .register_action(
270 move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
271 create_and_open_local_file(&paths::KEYMAP, cx, Default::default)
272 .detach_and_log_err(cx);
273 },
274 )
275 .register_action(
276 move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
277 create_and_open_local_file(&paths::SETTINGS, cx, || {
278 settings::initial_user_settings_content().as_ref().into()
279 })
280 .detach_and_log_err(cx);
281 },
282 )
283 .register_action(open_local_settings_file)
284 .register_action(
285 move |workspace: &mut Workspace,
286 _: &OpenDefaultKeymap,
287 cx: &mut ViewContext<Workspace>| {
288 open_bundled_file(
289 workspace,
290 settings::default_keymap(),
291 "Default Key Bindings",
292 "JSON",
293 cx,
294 );
295 },
296 )
297 .register_action(
298 move |workspace: &mut Workspace,
299 _: &OpenDefaultSettings,
300 cx: &mut ViewContext<Workspace>| {
301 open_bundled_file(
302 workspace,
303 settings::default_settings(),
304 "Default Settings",
305 "JSON",
306 cx,
307 );
308 },
309 )
310 //todo!()
311 // cx.add_action({
312 // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
313 // let app_state = workspace.app_state().clone();
314 // let markdown = app_state.languages.language_for_name("JSON");
315 // let window = cx.window();
316 // cx.spawn(|workspace, mut cx| async move {
317 // let markdown = markdown.await.log_err();
318 // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
319 // anyhow!("could not debug elements for window {}", window.id())
320 // })?)
321 // .unwrap();
322 // workspace
323 // .update(&mut cx, |workspace, cx| {
324 // workspace.with_local_workspace(cx, move |workspace, cx| {
325 // let project = workspace.project().clone();
326 // let buffer = project
327 // .update(cx, |project, cx| {
328 // project.create_buffer(&content, markdown, cx)
329 // })
330 // .expect("creating buffers on a local workspace always succeeds");
331 // let buffer = cx.add_model(|cx| {
332 // MultiBuffer::singleton(buffer, cx)
333 // .with_title("Debug Elements".into())
334 // });
335 // workspace.add_item(
336 // Box::new(cx.add_view(|cx| {
337 // Editor::for_multibuffer(buffer, Some(project.clone()), cx)
338 // })),
339 // cx,
340 // );
341 // })
342 // })?
343 // .await
344 // })
345 // .detach_and_log_err(cx);
346 // }
347 // });
348 // .register_action(
349 // |workspace: &mut Workspace,
350 // _: &project_panel::ToggleFocus,
351 // cx: &mut ViewContext<Workspace>| {
352 // workspace.toggle_panel_focus::<ProjectPanel>(cx);
353 // },
354 // );
355 // cx.add_action(
356 // |workspace: &mut Workspace,
357 // _: &collab_ui::collab_panel::ToggleFocus,
358 // cx: &mut ViewContext<Workspace>| {
359 // workspace.toggle_panel_focus::<collab_ui::collab_panel::CollabPanel>(cx);
360 // },
361 // );
362 // cx.add_action(
363 // |workspace: &mut Workspace,
364 // _: &collab_ui::chat_panel::ToggleFocus,
365 // cx: &mut ViewContext<Workspace>| {
366 // workspace.toggle_panel_focus::<collab_ui::chat_panel::ChatPanel>(cx);
367 // },
368 // );
369 // cx.add_action(
370 // |workspace: &mut Workspace,
371 // _: &collab_ui::notification_panel::ToggleFocus,
372 // cx: &mut ViewContext<Workspace>| {
373 // workspace.toggle_panel_focus::<collab_ui::notification_panel::NotificationPanel>(cx);
374 // },
375 // );
376 // cx.add_action(
377 // |workspace: &mut Workspace,
378 // _: &terminal_panel::ToggleFocus,
379 // cx: &mut ViewContext<Workspace>| {
380 // workspace.toggle_panel_focus::<TerminalPanel>(cx);
381 // },
382 // );
383 .register_action({
384 let app_state = Arc::downgrade(&app_state);
385 move |_, _: &NewWindow, cx| {
386 if let Some(app_state) = app_state.upgrade() {
387 open_new(&app_state, cx, |workspace, cx| {
388 Editor::new_file(workspace, &Default::default(), cx)
389 })
390 .detach();
391 }
392 }
393 })
394 .register_action({
395 let app_state = Arc::downgrade(&app_state);
396 move |_, _: &NewFile, cx| {
397 if let Some(app_state) = app_state.upgrade() {
398 open_new(&app_state, cx, |workspace, cx| {
399 Editor::new_file(workspace, &Default::default(), cx)
400 })
401 .detach();
402 }
403 }
404 });
405
406 workspace.focus_handle(cx).focus(cx);
407 //todo!()
408 // load_default_keymap(cx);
409 })
410 .detach();
411}
412
413fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
414 pane.update(cx, |pane, cx| {
415 pane.toolbar().update(cx, |toolbar, cx| {
416 let breadcrumbs = cx.build_view(|_| Breadcrumbs::new(workspace));
417 toolbar.add_item(breadcrumbs, cx);
418 let buffer_search_bar = cx.build_view(search::BufferSearchBar::new);
419 toolbar.add_item(buffer_search_bar.clone(), cx);
420
421 let quick_action_bar =
422 cx.build_view(|_| QuickActionBar::new(buffer_search_bar, workspace));
423 toolbar.add_item(quick_action_bar, cx);
424 let diagnostic_editor_controls = cx.build_view(|_| diagnostics::ToolbarControls::new());
425 // toolbar.add_item(diagnostic_editor_controls, cx);
426 // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
427 // toolbar.add_item(project_search_bar, cx);
428 // let submit_feedback_button =
429 // cx.add_view(|_| SubmitFeedbackButton::new());
430 // toolbar.add_item(submit_feedback_button, cx);
431 // let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
432 // toolbar.add_item(feedback_info_text, cx);
433 // let lsp_log_item =
434 // cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
435 // toolbar.add_item(lsp_log_item, cx);
436 // let syntax_tree_item = cx
437 // .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
438 // toolbar.add_item(syntax_tree_item, cx);
439 })
440 });
441}
442
443fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
444 use std::fmt::Write as _;
445
446 let app_name = cx.global::<ReleaseChannel>().display_name();
447 let version = env!("CARGO_PKG_VERSION");
448 let mut message = format!("{app_name} {version}");
449 if let Some(sha) = cx.try_global::<AppCommitSha>() {
450 write!(&mut message, "\n\n{}", sha.0).unwrap();
451 }
452
453 let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]);
454 cx.foreground_executor()
455 .spawn(async {
456 prompt.await.ok();
457 })
458 .detach();
459}
460
461fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
462 let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
463 cx.spawn(|_, mut cx| async move {
464 let mut workspace_windows = cx.update(|_, cx| {
465 cx.windows()
466 .into_iter()
467 .filter_map(|window| window.downcast::<Workspace>())
468 .collect::<Vec<_>>()
469 })?;
470
471 // If multiple windows have unsaved changes, and need a save prompt,
472 // prompt in the active window before switching to a different window.
473 cx.update(|_, cx| {
474 workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
475 })
476 .log_err();
477
478 if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
479 let answer = cx
480 .update(|_, cx| {
481 cx.prompt(
482 PromptLevel::Info,
483 "Are you sure you want to quit?",
484 &["Quit", "Cancel"],
485 )
486 })
487 .log_err();
488
489 if let Some(mut answer) = answer {
490 let answer = answer.await.ok();
491 if answer != Some(0) {
492 return Ok(());
493 }
494 }
495 }
496
497 // If the user cancels any save prompt, then keep the app open.
498 for window in workspace_windows {
499 if let Some(should_close) = window
500 .update(&mut cx, |workspace, cx| {
501 workspace.prepare_to_close(true, cx)
502 })
503 .log_err()
504 {
505 if !should_close.await? {
506 return Ok(());
507 }
508 }
509 }
510 cx.update(|_, cx| {
511 cx.quit();
512 })?;
513 anyhow::Ok(())
514 })
515 .detach_and_log_err(cx);
516}
517
518fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
519 const MAX_LINES: usize = 1000;
520 workspace
521 .with_local_workspace(cx, move |workspace, cx| {
522 let fs = workspace.app_state().fs.clone();
523 cx.spawn(|workspace, mut cx| async move {
524 let (old_log, new_log) =
525 futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG));
526
527 let mut lines = VecDeque::with_capacity(MAX_LINES);
528 for line in old_log
529 .iter()
530 .flat_map(|log| log.lines())
531 .chain(new_log.iter().flat_map(|log| log.lines()))
532 {
533 if lines.len() == MAX_LINES {
534 lines.pop_front();
535 }
536 lines.push_back(line);
537 }
538 let log = lines
539 .into_iter()
540 .flat_map(|line| [line, "\n"])
541 .collect::<String>();
542
543 workspace
544 .update(&mut cx, |workspace, cx| {
545 let project = workspace.project().clone();
546 let buffer = project
547 .update(cx, |project, cx| project.create_buffer("", None, cx))
548 .expect("creating buffers on a local workspace always succeeds");
549 buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
550
551 let buffer = cx.build_model(|cx| {
552 MultiBuffer::singleton(buffer, cx).with_title("Log".into())
553 });
554 workspace.add_item(
555 Box::new(cx.build_view(|cx| {
556 Editor::for_multibuffer(buffer, Some(project), cx)
557 })),
558 cx,
559 );
560 })
561 .log_err();
562 })
563 .detach();
564 })
565 .detach();
566}
567
568fn open_local_settings_file(
569 workspace: &mut Workspace,
570 _: &OpenLocalSettings,
571 cx: &mut ViewContext<Workspace>,
572) {
573 let project = workspace.project().clone();
574 let worktree = project
575 .read(cx)
576 .visible_worktrees(cx)
577 .find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree));
578 if let Some(worktree) = worktree {
579 let tree_id = worktree.read(cx).id();
580 cx.spawn(|workspace, mut cx| async move {
581 let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH;
582
583 if let Some(dir_path) = file_path.parent() {
584 if worktree.update(&mut cx, |tree, _| tree.entry_for_path(dir_path).is_none())? {
585 project
586 .update(&mut cx, |project, cx| {
587 project.create_entry((tree_id, dir_path), true, cx)
588 })?
589 .await
590 .context("worktree was removed")?;
591 }
592 }
593
594 if worktree.update(&mut cx, |tree, _| tree.entry_for_path(file_path).is_none())? {
595 project
596 .update(&mut cx, |project, cx| {
597 project.create_entry((tree_id, file_path), false, cx)
598 })?
599 .await
600 .context("worktree was removed")?;
601 }
602
603 let editor = workspace
604 .update(&mut cx, |workspace, cx| {
605 workspace.open_path((tree_id, file_path), None, true, cx)
606 })?
607 .await?
608 .downcast::<Editor>()
609 .ok_or_else(|| anyhow!("unexpected item type"))?;
610
611 editor
612 .downgrade()
613 .update(&mut cx, |editor, cx| {
614 if let Some(buffer) = editor.buffer().read(cx).as_singleton() {
615 if buffer.read(cx).is_empty() {
616 buffer.update(cx, |buffer, cx| {
617 buffer.edit([(0..0, initial_local_settings_content())], None, cx)
618 });
619 }
620 }
621 })
622 .ok();
623
624 anyhow::Ok(())
625 })
626 .detach();
627 } else {
628 workspace.show_notification(0, cx, |cx| {
629 cx.build_view(|_| MessageNotification::new("This project has no folders open."))
630 })
631 }
632}
633
634fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
635 workspace.with_local_workspace(cx, move |workspace, cx| {
636 let app_state = workspace.app_state().clone();
637 cx.spawn(|workspace, mut cx| async move {
638 async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
639 let path = app_state.client.telemetry().log_file_path()?;
640 app_state.fs.load(&path).await.log_err()
641 }
642
643 let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string());
644
645 const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024;
646 let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN);
647 if let Some(newline_offset) = log[start_offset..].find('\n') {
648 start_offset += newline_offset + 1;
649 }
650 let log_suffix = &log[start_offset..];
651 let json = app_state.languages.language_for_name("JSON").await.log_err();
652
653 workspace.update(&mut cx, |workspace, cx| {
654 let project = workspace.project().clone();
655 let buffer = project
656 .update(cx, |project, cx| project.create_buffer("", None, cx))
657 .expect("creating buffers on a local workspace always succeeds");
658 buffer.update(cx, |buffer, cx| {
659 buffer.set_language(json, cx);
660 buffer.edit(
661 [(
662 0..0,
663 concat!(
664 "// Zed collects anonymous usage data to help us understand how people are using the app.\n",
665 "// Telemetry can be disabled via the `settings.json` file.\n",
666 "// Here is the data that has been reported for the current session:\n",
667 "\n"
668 ),
669 )],
670 None,
671 cx,
672 );
673 buffer.edit([(buffer.len()..buffer.len(), log_suffix)], None, cx);
674 });
675
676 let buffer = cx.build_model(|cx| {
677 MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into())
678 });
679 workspace.add_item(
680 Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
681 cx,
682 );
683 }).log_err()?;
684
685 Some(())
686 })
687 .detach();
688 }).detach();
689}
690
691fn open_bundled_file(
692 workspace: &mut Workspace,
693 text: Cow<'static, str>,
694 title: &'static str,
695 language: &'static str,
696 cx: &mut ViewContext<Workspace>,
697) {
698 let language = workspace.app_state().languages.language_for_name(language);
699 cx.spawn(|workspace, mut cx| async move {
700 let language = language.await.log_err();
701 workspace
702 .update(&mut cx, |workspace, cx| {
703 workspace.with_local_workspace(cx, |workspace, cx| {
704 let project = workspace.project();
705 let buffer = project.update(cx, move |project, cx| {
706 project
707 .create_buffer(text.as_ref(), language, cx)
708 .expect("creating buffers on a local workspace always succeeds")
709 });
710 let buffer = cx.build_model(|cx| {
711 MultiBuffer::singleton(buffer, cx).with_title(title.into())
712 });
713 workspace.add_item(
714 Box::new(cx.build_view(|cx| {
715 Editor::for_multibuffer(buffer, Some(project.clone()), cx)
716 })),
717 cx,
718 );
719 })
720 })?
721 .await
722 })
723 .detach_and_log_err(cx);
724}
725
726// todo!()
727// #[cfg(test)]
728// mod tests {
729// use super::*;
730// use assets::Assets;
731// use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
732// use fs::{FakeFs, Fs};
733// use gpui::{
734// actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
735// AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
736// };
737// use language::LanguageRegistry;
738// use project::{project_settings::ProjectSettings, Project, ProjectPath};
739// use serde_json::json;
740// use settings::{handle_settings_file_changes, watch_config_file, SettingsStore};
741// use std::{
742// collections::HashSet,
743// path::{Path, PathBuf},
744// };
745// use theme::{ThemeRegistry, ThemeSettings};
746// use workspace::{
747// item::{Item, ItemHandle},
748// open_new, open_paths, pane, NewFile, SaveIntent, SplitDirection, WorkspaceHandle,
749// };
750
751// #[gpui::test]
752// async fn test_open_paths_action(cx: &mut TestAppContext) {
753// let app_state = init_test(cx);
754// app_state
755// .fs
756// .as_fake()
757// .insert_tree(
758// "/root",
759// json!({
760// "a": {
761// "aa": null,
762// "ab": null,
763// },
764// "b": {
765// "ba": null,
766// "bb": null,
767// },
768// "c": {
769// "ca": null,
770// "cb": null,
771// },
772// "d": {
773// "da": null,
774// "db": null,
775// },
776// }),
777// )
778// .await;
779
780// cx.update(|cx| {
781// open_paths(
782// &[PathBuf::from("/root/a"), PathBuf::from("/root/b")],
783// &app_state,
784// None,
785// cx,
786// )
787// })
788// .await
789// .unwrap();
790// assert_eq!(cx.windows().len(), 1);
791
792// cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
793// .await
794// .unwrap();
795// assert_eq!(cx.windows().len(), 1);
796// let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
797// workspace_1.update(cx, |workspace, cx| {
798// assert_eq!(workspace.worktrees(cx).count(), 2);
799// assert!(workspace.left_dock().read(cx).is_open());
800// assert!(workspace.active_pane().is_focused(cx));
801// });
802
803// cx.update(|cx| {
804// open_paths(
805// &[PathBuf::from("/root/b"), PathBuf::from("/root/c")],
806// &app_state,
807// None,
808// cx,
809// )
810// })
811// .await
812// .unwrap();
813// assert_eq!(cx.windows().len(), 2);
814
815// // Replace existing windows
816// let window = cx.windows()[0].downcast::<Workspace>().unwrap();
817// cx.update(|cx| {
818// open_paths(
819// &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
820// &app_state,
821// Some(window),
822// cx,
823// )
824// })
825// .await
826// .unwrap();
827// assert_eq!(cx.windows().len(), 2);
828// let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
829// workspace_1.update(cx, |workspace, cx| {
830// assert_eq!(
831// workspace
832// .worktrees(cx)
833// .map(|w| w.read(cx).abs_path())
834// .collect::<Vec<_>>(),
835// &[Path::new("/root/c").into(), Path::new("/root/d").into()]
836// );
837// assert!(workspace.left_dock().read(cx).is_open());
838// assert!(workspace.active_pane().is_focused(cx));
839// });
840// }
841
842// #[gpui::test]
843// async fn test_window_edit_state(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
844// let app_state = init_test(cx);
845// app_state
846// .fs
847// .as_fake()
848// .insert_tree("/root", json!({"a": "hey"}))
849// .await;
850
851// cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
852// .await
853// .unwrap();
854// assert_eq!(cx.windows().len(), 1);
855
856// // When opening the workspace, the window is not in a edited state.
857// let window = cx.windows()[0].downcast::<Workspace>().unwrap();
858// let workspace = window.root(cx);
859// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
860// let editor = workspace.read_with(cx, |workspace, cx| {
861// workspace
862// .active_item(cx)
863// .unwrap()
864// .downcast::<Editor>()
865// .unwrap()
866// });
867// assert!(!window.is_edited(cx));
868
869// // Editing a buffer marks the window as edited.
870// editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
871// assert!(window.is_edited(cx));
872
873// // Undoing the edit restores the window's edited state.
874// editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx));
875// assert!(!window.is_edited(cx));
876
877// // Redoing the edit marks the window as edited again.
878// editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx));
879// assert!(window.is_edited(cx));
880
881// // Closing the item restores the window's edited state.
882// let close = pane.update(cx, |pane, cx| {
883// drop(editor);
884// pane.close_active_item(&Default::default(), cx).unwrap()
885// });
886// executor.run_until_parked();
887
888// window.simulate_prompt_answer(1, cx);
889// close.await.unwrap();
890// assert!(!window.is_edited(cx));
891
892// // Opening the buffer again doesn't impact the window's edited state.
893// cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
894// .await
895// .unwrap();
896// let editor = workspace.read_with(cx, |workspace, cx| {
897// workspace
898// .active_item(cx)
899// .unwrap()
900// .downcast::<Editor>()
901// .unwrap()
902// });
903// assert!(!window.is_edited(cx));
904
905// // Editing the buffer marks the window as edited.
906// editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
907// assert!(window.is_edited(cx));
908
909// // Ensure closing the window via the mouse gets preempted due to the
910// // buffer having unsaved changes.
911// assert!(!window.simulate_close(cx));
912// executor.run_until_parked();
913// assert_eq!(cx.windows().len(), 1);
914
915// // The window is successfully closed after the user dismisses the prompt.
916// window.simulate_prompt_answer(1, cx);
917// executor.run_until_parked();
918// assert_eq!(cx.windows().len(), 0);
919// }
920
921// #[gpui::test]
922// async fn test_new_empty_workspace(cx: &mut TestAppContext) {
923// let app_state = init_test(cx);
924// cx.update(|cx| {
925// open_new(&app_state, cx, |workspace, cx| {
926// Editor::new_file(workspace, &Default::default(), cx)
927// })
928// })
929// .await;
930
931// let window = cx
932// .windows()
933// .first()
934// .unwrap()
935// .downcast::<Workspace>()
936// .unwrap();
937// let workspace = window.root(cx);
938
939// let editor = workspace.update(cx, |workspace, cx| {
940// workspace
941// .active_item(cx)
942// .unwrap()
943// .downcast::<editor::Editor>()
944// .unwrap()
945// });
946
947// editor.update(cx, |editor, cx| {
948// assert!(editor.text(cx).is_empty());
949// assert!(!editor.is_dirty(cx));
950// });
951
952// let save_task = workspace.update(cx, |workspace, cx| {
953// workspace.save_active_item(SaveIntent::Save, cx)
954// });
955// app_state.fs.create_dir(Path::new("/root")).await.unwrap();
956// cx.foreground().run_until_parked();
957// cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name")));
958// save_task.await.unwrap();
959// editor.read_with(cx, |editor, cx| {
960// assert!(!editor.is_dirty(cx));
961// assert_eq!(editor.title(cx), "the-new-name");
962// });
963// }
964
965// #[gpui::test]
966// async fn test_open_entry(cx: &mut TestAppContext) {
967// let app_state = init_test(cx);
968// app_state
969// .fs
970// .as_fake()
971// .insert_tree(
972// "/root",
973// json!({
974// "a": {
975// "file1": "contents 1",
976// "file2": "contents 2",
977// "file3": "contents 3",
978// },
979// }),
980// )
981// .await;
982
983// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
984// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
985// let workspace = window.root(cx);
986
987// let entries = cx.read(|cx| workspace.file_project_paths(cx));
988// let file1 = entries[0].clone();
989// let file2 = entries[1].clone();
990// let file3 = entries[2].clone();
991
992// // Open the first entry
993// let entry_1 = workspace
994// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
995// .await
996// .unwrap();
997// cx.read(|cx| {
998// let pane = workspace.read(cx).active_pane().read(cx);
999// assert_eq!(
1000// pane.active_item().unwrap().project_path(cx),
1001// Some(file1.clone())
1002// );
1003// assert_eq!(pane.items_len(), 1);
1004// });
1005
1006// // Open the second entry
1007// workspace
1008// .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1009// .await
1010// .unwrap();
1011// cx.read(|cx| {
1012// let pane = workspace.read(cx).active_pane().read(cx);
1013// assert_eq!(
1014// pane.active_item().unwrap().project_path(cx),
1015// Some(file2.clone())
1016// );
1017// assert_eq!(pane.items_len(), 2);
1018// });
1019
1020// // Open the first entry again. The existing pane item is activated.
1021// let entry_1b = workspace
1022// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1023// .await
1024// .unwrap();
1025// assert_eq!(entry_1.id(), entry_1b.id());
1026
1027// cx.read(|cx| {
1028// let pane = workspace.read(cx).active_pane().read(cx);
1029// assert_eq!(
1030// pane.active_item().unwrap().project_path(cx),
1031// Some(file1.clone())
1032// );
1033// assert_eq!(pane.items_len(), 2);
1034// });
1035
1036// // Split the pane with the first entry, then open the second entry again.
1037// workspace
1038// .update(cx, |w, cx| {
1039// w.split_and_clone(w.active_pane().clone(), SplitDirection::Right, cx);
1040// w.open_path(file2.clone(), None, true, cx)
1041// })
1042// .await
1043// .unwrap();
1044
1045// workspace.read_with(cx, |w, cx| {
1046// assert_eq!(
1047// w.active_pane()
1048// .read(cx)
1049// .active_item()
1050// .unwrap()
1051// .project_path(cx),
1052// Some(file2.clone())
1053// );
1054// });
1055
1056// // Open the third entry twice concurrently. Only one pane item is added.
1057// let (t1, t2) = workspace.update(cx, |w, cx| {
1058// (
1059// w.open_path(file3.clone(), None, true, cx),
1060// w.open_path(file3.clone(), None, true, cx),
1061// )
1062// });
1063// t1.await.unwrap();
1064// t2.await.unwrap();
1065// cx.read(|cx| {
1066// let pane = workspace.read(cx).active_pane().read(cx);
1067// assert_eq!(
1068// pane.active_item().unwrap().project_path(cx),
1069// Some(file3.clone())
1070// );
1071// let pane_entries = pane
1072// .items()
1073// .map(|i| i.project_path(cx).unwrap())
1074// .collect::<Vec<_>>();
1075// assert_eq!(pane_entries, &[file1, file2, file3]);
1076// });
1077// }
1078
1079// #[gpui::test]
1080// async fn test_open_paths(cx: &mut TestAppContext) {
1081// let app_state = init_test(cx);
1082
1083// app_state
1084// .fs
1085// .as_fake()
1086// .insert_tree(
1087// "/",
1088// json!({
1089// "dir1": {
1090// "a.txt": ""
1091// },
1092// "dir2": {
1093// "b.txt": ""
1094// },
1095// "dir3": {
1096// "c.txt": ""
1097// },
1098// "d.txt": ""
1099// }),
1100// )
1101// .await;
1102
1103// cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
1104// .await
1105// .unwrap();
1106// assert_eq!(cx.windows().len(), 1);
1107// let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
1108
1109// #[track_caller]
1110// fn assert_project_panel_selection(
1111// workspace: &Workspace,
1112// expected_worktree_path: &Path,
1113// expected_entry_path: &Path,
1114// cx: &AppContext,
1115// ) {
1116// let project_panel = [
1117// workspace.left_dock().read(cx).panel::<ProjectPanel>(),
1118// workspace.right_dock().read(cx).panel::<ProjectPanel>(),
1119// workspace.bottom_dock().read(cx).panel::<ProjectPanel>(),
1120// ]
1121// .into_iter()
1122// .find_map(std::convert::identity)
1123// .expect("found no project panels")
1124// .read(cx);
1125// let (selected_worktree, selected_entry) = project_panel
1126// .selected_entry(cx)
1127// .expect("project panel should have a selected entry");
1128// assert_eq!(
1129// selected_worktree.abs_path().as_ref(),
1130// expected_worktree_path,
1131// "Unexpected project panel selected worktree path"
1132// );
1133// assert_eq!(
1134// selected_entry.path.as_ref(),
1135// expected_entry_path,
1136// "Unexpected project panel selected entry path"
1137// );
1138// }
1139
1140// // Open a file within an existing worktree.
1141// workspace
1142// .update(cx, |view, cx| {
1143// view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
1144// })
1145// .await;
1146// cx.read(|cx| {
1147// let workspace = workspace.read(cx);
1148// assert_project_panel_selection(workspace, Path::new("/dir1"), Path::new("a.txt"), cx);
1149// assert_eq!(
1150// workspace
1151// .active_pane()
1152// .read(cx)
1153// .active_item()
1154// .unwrap()
1155// .as_any()
1156// .downcast_ref::<Editor>()
1157// .unwrap()
1158// .read(cx)
1159// .title(cx),
1160// "a.txt"
1161// );
1162// });
1163
1164// // Open a file outside of any existing worktree.
1165// workspace
1166// .update(cx, |view, cx| {
1167// view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
1168// })
1169// .await;
1170// cx.read(|cx| {
1171// let workspace = workspace.read(cx);
1172// assert_project_panel_selection(workspace, Path::new("/dir2/b.txt"), Path::new(""), cx);
1173// let worktree_roots = workspace
1174// .worktrees(cx)
1175// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1176// .collect::<HashSet<_>>();
1177// assert_eq!(
1178// worktree_roots,
1179// vec!["/dir1", "/dir2/b.txt"]
1180// .into_iter()
1181// .map(Path::new)
1182// .collect(),
1183// );
1184// assert_eq!(
1185// workspace
1186// .active_pane()
1187// .read(cx)
1188// .active_item()
1189// .unwrap()
1190// .as_any()
1191// .downcast_ref::<Editor>()
1192// .unwrap()
1193// .read(cx)
1194// .title(cx),
1195// "b.txt"
1196// );
1197// });
1198
1199// // Ensure opening a directory and one of its children only adds one worktree.
1200// workspace
1201// .update(cx, |view, cx| {
1202// view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
1203// })
1204// .await;
1205// cx.read(|cx| {
1206// let workspace = workspace.read(cx);
1207// assert_project_panel_selection(workspace, Path::new("/dir3"), Path::new("c.txt"), cx);
1208// let worktree_roots = workspace
1209// .worktrees(cx)
1210// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1211// .collect::<HashSet<_>>();
1212// assert_eq!(
1213// worktree_roots,
1214// vec!["/dir1", "/dir2/b.txt", "/dir3"]
1215// .into_iter()
1216// .map(Path::new)
1217// .collect(),
1218// );
1219// assert_eq!(
1220// workspace
1221// .active_pane()
1222// .read(cx)
1223// .active_item()
1224// .unwrap()
1225// .as_any()
1226// .downcast_ref::<Editor>()
1227// .unwrap()
1228// .read(cx)
1229// .title(cx),
1230// "c.txt"
1231// );
1232// });
1233
1234// // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
1235// workspace
1236// .update(cx, |view, cx| {
1237// view.open_paths(vec!["/d.txt".into()], false, cx)
1238// })
1239// .await;
1240// cx.read(|cx| {
1241// let workspace = workspace.read(cx);
1242// assert_project_panel_selection(workspace, Path::new("/d.txt"), Path::new(""), cx);
1243// let worktree_roots = workspace
1244// .worktrees(cx)
1245// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1246// .collect::<HashSet<_>>();
1247// assert_eq!(
1248// worktree_roots,
1249// vec!["/dir1", "/dir2/b.txt", "/dir3", "/d.txt"]
1250// .into_iter()
1251// .map(Path::new)
1252// .collect(),
1253// );
1254
1255// let visible_worktree_roots = workspace
1256// .visible_worktrees(cx)
1257// .map(|w| w.read(cx).as_local().unwrap().abs_path().as_ref())
1258// .collect::<HashSet<_>>();
1259// assert_eq!(
1260// visible_worktree_roots,
1261// vec!["/dir1", "/dir2/b.txt", "/dir3"]
1262// .into_iter()
1263// .map(Path::new)
1264// .collect(),
1265// );
1266
1267// assert_eq!(
1268// workspace
1269// .active_pane()
1270// .read(cx)
1271// .active_item()
1272// .unwrap()
1273// .as_any()
1274// .downcast_ref::<Editor>()
1275// .unwrap()
1276// .read(cx)
1277// .title(cx),
1278// "d.txt"
1279// );
1280// });
1281// }
1282
1283// #[gpui::test]
1284// async fn test_opening_excluded_paths(cx: &mut TestAppContext) {
1285// let app_state = init_test(cx);
1286// cx.update(|cx| {
1287// cx.update_global::<SettingsStore, _, _>(|store, cx| {
1288// store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1289// project_settings.file_scan_exclusions =
1290// Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
1291// });
1292// });
1293// });
1294// app_state
1295// .fs
1296// .as_fake()
1297// .insert_tree(
1298// "/root",
1299// json!({
1300// ".gitignore": "ignored_dir\n",
1301// ".git": {
1302// "HEAD": "ref: refs/heads/main",
1303// },
1304// "regular_dir": {
1305// "file": "regular file contents",
1306// },
1307// "ignored_dir": {
1308// "ignored_subdir": {
1309// "file": "ignored subfile contents",
1310// },
1311// "file": "ignored file contents",
1312// },
1313// "excluded_dir": {
1314// "file": "excluded file contents",
1315// },
1316// }),
1317// )
1318// .await;
1319
1320// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1321// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1322// let workspace = window.root(cx);
1323
1324// let initial_entries = cx.read(|cx| workspace.file_project_paths(cx));
1325// let paths_to_open = [
1326// Path::new("/root/excluded_dir/file").to_path_buf(),
1327// Path::new("/root/.git/HEAD").to_path_buf(),
1328// Path::new("/root/excluded_dir/ignored_subdir").to_path_buf(),
1329// ];
1330// let (opened_workspace, new_items) = cx
1331// .update(|cx| workspace::open_paths(&paths_to_open, &app_state, None, cx))
1332// .await
1333// .unwrap();
1334
1335// assert_eq!(
1336// opened_workspace.id(),
1337// workspace.id(),
1338// "Excluded files in subfolders of a workspace root should be opened in the workspace"
1339// );
1340// let mut opened_paths = cx.read(|cx| {
1341// assert_eq!(
1342// new_items.len(),
1343// paths_to_open.len(),
1344// "Expect to get the same number of opened items as submitted paths to open"
1345// );
1346// new_items
1347// .iter()
1348// .zip(paths_to_open.iter())
1349// .map(|(i, path)| {
1350// match i {
1351// Some(Ok(i)) => {
1352// Some(i.project_path(cx).map(|p| p.path.display().to_string()))
1353// }
1354// Some(Err(e)) => panic!("Excluded file {path:?} failed to open: {e:?}"),
1355// None => None,
1356// }
1357// .flatten()
1358// })
1359// .collect::<Vec<_>>()
1360// });
1361// opened_paths.sort();
1362// assert_eq!(
1363// opened_paths,
1364// vec![
1365// None,
1366// Some(".git/HEAD".to_string()),
1367// Some("excluded_dir/file".to_string()),
1368// ],
1369// "Excluded files should get opened, excluded dir should not get opened"
1370// );
1371
1372// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1373// assert_eq!(
1374// initial_entries, entries,
1375// "Workspace entries should not change after opening excluded files and directories paths"
1376// );
1377
1378// cx.read(|cx| {
1379// let pane = workspace.read(cx).active_pane().read(cx);
1380// let mut opened_buffer_paths = pane
1381// .items()
1382// .map(|i| {
1383// i.project_path(cx)
1384// .expect("all excluded files that got open should have a path")
1385// .path
1386// .display()
1387// .to_string()
1388// })
1389// .collect::<Vec<_>>();
1390// opened_buffer_paths.sort();
1391// assert_eq!(
1392// opened_buffer_paths,
1393// vec![".git/HEAD".to_string(), "excluded_dir/file".to_string()],
1394// "Despite not being present in the worktrees, buffers for excluded files are opened and added to the pane"
1395// );
1396// });
1397// }
1398
1399// #[gpui::test]
1400// async fn test_save_conflicting_item(cx: &mut TestAppContext) {
1401// let app_state = init_test(cx);
1402// app_state
1403// .fs
1404// .as_fake()
1405// .insert_tree("/root", json!({ "a.txt": "" }))
1406// .await;
1407
1408// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1409// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1410// let workspace = window.root(cx);
1411
1412// // Open a file within an existing worktree.
1413// workspace
1414// .update(cx, |view, cx| {
1415// view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
1416// })
1417// .await;
1418// let editor = cx.read(|cx| {
1419// let pane = workspace.read(cx).active_pane().read(cx);
1420// let item = pane.active_item().unwrap();
1421// item.downcast::<Editor>().unwrap()
1422// });
1423
1424// editor.update(cx, |editor, cx| editor.handle_input("x", cx));
1425// app_state
1426// .fs
1427// .as_fake()
1428// .insert_file("/root/a.txt", "changed".to_string())
1429// .await;
1430// editor
1431// .condition(cx, |editor, cx| editor.has_conflict(cx))
1432// .await;
1433// cx.read(|cx| assert!(editor.is_dirty(cx)));
1434
1435// let save_task = workspace.update(cx, |workspace, cx| {
1436// workspace.save_active_item(SaveIntent::Save, cx)
1437// });
1438// cx.foreground().run_until_parked();
1439// window.simulate_prompt_answer(0, cx);
1440// save_task.await.unwrap();
1441// editor.read_with(cx, |editor, cx| {
1442// assert!(!editor.is_dirty(cx));
1443// assert!(!editor.has_conflict(cx));
1444// });
1445// }
1446
1447// #[gpui::test]
1448// async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
1449// let app_state = init_test(cx);
1450// app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1451
1452// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1453// project.update(cx, |project, _| project.languages().add(rust_lang()));
1454// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1455// let workspace = window.root(cx);
1456// let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
1457
1458// // Create a new untitled buffer
1459// cx.dispatch_action(window.into(), NewFile);
1460// let editor = workspace.read_with(cx, |workspace, cx| {
1461// workspace
1462// .active_item(cx)
1463// .unwrap()
1464// .downcast::<Editor>()
1465// .unwrap()
1466// });
1467
1468// editor.update(cx, |editor, cx| {
1469// assert!(!editor.is_dirty(cx));
1470// assert_eq!(editor.title(cx), "untitled");
1471// assert!(Arc::ptr_eq(
1472// &editor.language_at(0, cx).unwrap(),
1473// &languages::PLAIN_TEXT
1474// ));
1475// editor.handle_input("hi", cx);
1476// assert!(editor.is_dirty(cx));
1477// });
1478
1479// // Save the buffer. This prompts for a filename.
1480// let save_task = workspace.update(cx, |workspace, cx| {
1481// workspace.save_active_item(SaveIntent::Save, cx)
1482// });
1483// cx.foreground().run_until_parked();
1484// cx.simulate_new_path_selection(|parent_dir| {
1485// assert_eq!(parent_dir, Path::new("/root"));
1486// Some(parent_dir.join("the-new-name.rs"))
1487// });
1488// cx.read(|cx| {
1489// assert!(editor.is_dirty(cx));
1490// assert_eq!(editor.read(cx).title(cx), "untitled");
1491// });
1492
1493// // When the save completes, the buffer's title is updated and the language is assigned based
1494// // on the path.
1495// save_task.await.unwrap();
1496// editor.read_with(cx, |editor, cx| {
1497// assert!(!editor.is_dirty(cx));
1498// assert_eq!(editor.title(cx), "the-new-name.rs");
1499// assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust");
1500// });
1501
1502// // Edit the file and save it again. This time, there is no filename prompt.
1503// editor.update(cx, |editor, cx| {
1504// editor.handle_input(" there", cx);
1505// assert!(editor.is_dirty(cx));
1506// });
1507// let save_task = workspace.update(cx, |workspace, cx| {
1508// workspace.save_active_item(SaveIntent::Save, cx)
1509// });
1510// save_task.await.unwrap();
1511// assert!(!cx.did_prompt_for_new_path());
1512// editor.read_with(cx, |editor, cx| {
1513// assert!(!editor.is_dirty(cx));
1514// assert_eq!(editor.title(cx), "the-new-name.rs")
1515// });
1516
1517// // Open the same newly-created file in another pane item. The new editor should reuse
1518// // the same buffer.
1519// cx.dispatch_action(window.into(), NewFile);
1520// workspace
1521// .update(cx, |workspace, cx| {
1522// workspace.split_and_clone(
1523// workspace.active_pane().clone(),
1524// SplitDirection::Right,
1525// cx,
1526// );
1527// workspace.open_path((worktree.read(cx).id(), "the-new-name.rs"), None, true, cx)
1528// })
1529// .await
1530// .unwrap();
1531// let editor2 = workspace.update(cx, |workspace, cx| {
1532// workspace
1533// .active_item(cx)
1534// .unwrap()
1535// .downcast::<Editor>()
1536// .unwrap()
1537// });
1538// cx.read(|cx| {
1539// assert_eq!(
1540// editor2.read(cx).buffer().read(cx).as_singleton().unwrap(),
1541// editor.read(cx).buffer().read(cx).as_singleton().unwrap()
1542// );
1543// })
1544// }
1545
1546// #[gpui::test]
1547// async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
1548// let app_state = init_test(cx);
1549// app_state.fs.create_dir(Path::new("/root")).await.unwrap();
1550
1551// let project = Project::test(app_state.fs.clone(), [], cx).await;
1552// project.update(cx, |project, _| project.languages().add(rust_lang()));
1553// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1554// let workspace = window.root(cx);
1555
1556// // Create a new untitled buffer
1557// cx.dispatch_action(window.into(), NewFile);
1558// let editor = workspace.read_with(cx, |workspace, cx| {
1559// workspace
1560// .active_item(cx)
1561// .unwrap()
1562// .downcast::<Editor>()
1563// .unwrap()
1564// });
1565
1566// editor.update(cx, |editor, cx| {
1567// assert!(Arc::ptr_eq(
1568// &editor.language_at(0, cx).unwrap(),
1569// &languages::PLAIN_TEXT
1570// ));
1571// editor.handle_input("hi", cx);
1572// assert!(editor.is_dirty(cx));
1573// });
1574
1575// // Save the buffer. This prompts for a filename.
1576// let save_task = workspace.update(cx, |workspace, cx| {
1577// workspace.save_active_item(SaveIntent::Save, cx)
1578// });
1579// cx.foreground().run_until_parked();
1580// cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name.rs")));
1581// save_task.await.unwrap();
1582// // The buffer is not dirty anymore and the language is assigned based on the path.
1583// editor.read_with(cx, |editor, cx| {
1584// assert!(!editor.is_dirty(cx));
1585// assert_eq!(editor.language_at(0, cx).unwrap().name().as_ref(), "Rust")
1586// });
1587// }
1588
1589// #[gpui::test]
1590// async fn test_pane_actions(cx: &mut TestAppContext) {
1591// let app_state = init_test(cx);
1592// app_state
1593// .fs
1594// .as_fake()
1595// .insert_tree(
1596// "/root",
1597// json!({
1598// "a": {
1599// "file1": "contents 1",
1600// "file2": "contents 2",
1601// "file3": "contents 3",
1602// },
1603// }),
1604// )
1605// .await;
1606
1607// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1608// let window = cx.add_window(|cx| Workspace::test_new(project, cx));
1609// let workspace = window.root(cx);
1610
1611// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1612// let file1 = entries[0].clone();
1613
1614// let pane_1 = cx.read(|cx| workspace.read(cx).active_pane().clone());
1615
1616// workspace
1617// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1618// .await
1619// .unwrap();
1620
1621// let (editor_1, buffer) = pane_1.update(cx, |pane_1, cx| {
1622// let editor = pane_1.active_item().unwrap().downcast::<Editor>().unwrap();
1623// assert_eq!(editor.project_path(cx), Some(file1.clone()));
1624// let buffer = editor.update(cx, |editor, cx| {
1625// editor.insert("dirt", cx);
1626// editor.buffer().downgrade()
1627// });
1628// (editor.downgrade(), buffer)
1629// });
1630
1631// cx.dispatch_action(window.into(), pane::SplitRight);
1632// let editor_2 = cx.update(|cx| {
1633// let pane_2 = workspace.read(cx).active_pane().clone();
1634// assert_ne!(pane_1, pane_2);
1635
1636// let pane2_item = pane_2.read(cx).active_item().unwrap();
1637// assert_eq!(pane2_item.project_path(cx), Some(file1.clone()));
1638
1639// pane2_item.downcast::<Editor>().unwrap().downgrade()
1640// });
1641// cx.dispatch_action(
1642// window.into(),
1643// workspace::CloseActiveItem { save_intent: None },
1644// );
1645
1646// cx.foreground().run_until_parked();
1647// workspace.read_with(cx, |workspace, _| {
1648// assert_eq!(workspace.panes().len(), 1);
1649// assert_eq!(workspace.active_pane(), &pane_1);
1650// });
1651
1652// cx.dispatch_action(
1653// window.into(),
1654// workspace::CloseActiveItem { save_intent: None },
1655// );
1656// cx.foreground().run_until_parked();
1657// window.simulate_prompt_answer(1, cx);
1658// cx.foreground().run_until_parked();
1659
1660// workspace.read_with(cx, |workspace, cx| {
1661// assert_eq!(workspace.panes().len(), 1);
1662// assert!(workspace.active_item(cx).is_none());
1663// });
1664
1665// cx.assert_dropped(editor_1);
1666// cx.assert_dropped(editor_2);
1667// cx.assert_dropped(buffer);
1668// }
1669
1670// #[gpui::test]
1671// async fn test_navigation(cx: &mut TestAppContext) {
1672// let app_state = init_test(cx);
1673// app_state
1674// .fs
1675// .as_fake()
1676// .insert_tree(
1677// "/root",
1678// json!({
1679// "a": {
1680// "file1": "contents 1\n".repeat(20),
1681// "file2": "contents 2\n".repeat(20),
1682// "file3": "contents 3\n".repeat(20),
1683// },
1684// }),
1685// )
1686// .await;
1687
1688// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1689// let workspace = cx
1690// .add_window(|cx| Workspace::test_new(project.clone(), cx))
1691// .root(cx);
1692// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1693
1694// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1695// let file1 = entries[0].clone();
1696// let file2 = entries[1].clone();
1697// let file3 = entries[2].clone();
1698
1699// let editor1 = workspace
1700// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1701// .await
1702// .unwrap()
1703// .downcast::<Editor>()
1704// .unwrap();
1705// editor1.update(cx, |editor, cx| {
1706// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1707// s.select_display_ranges([DisplayPoint::new(10, 0)..DisplayPoint::new(10, 0)])
1708// });
1709// });
1710// let editor2 = workspace
1711// .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1712// .await
1713// .unwrap()
1714// .downcast::<Editor>()
1715// .unwrap();
1716// let editor3 = workspace
1717// .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
1718// .await
1719// .unwrap()
1720// .downcast::<Editor>()
1721// .unwrap();
1722
1723// editor3
1724// .update(cx, |editor, cx| {
1725// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
1726// s.select_display_ranges([DisplayPoint::new(12, 0)..DisplayPoint::new(12, 0)])
1727// });
1728// editor.newline(&Default::default(), cx);
1729// editor.newline(&Default::default(), cx);
1730// editor.move_down(&Default::default(), cx);
1731// editor.move_down(&Default::default(), cx);
1732// editor.save(project.clone(), cx)
1733// })
1734// .await
1735// .unwrap();
1736// editor3.update(cx, |editor, cx| {
1737// editor.set_scroll_position(vec2f(0., 12.5), cx)
1738// });
1739// assert_eq!(
1740// active_location(&workspace, cx),
1741// (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1742// );
1743
1744// workspace
1745// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1746// .await
1747// .unwrap();
1748// assert_eq!(
1749// active_location(&workspace, cx),
1750// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1751// );
1752
1753// workspace
1754// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1755// .await
1756// .unwrap();
1757// assert_eq!(
1758// active_location(&workspace, cx),
1759// (file2.clone(), DisplayPoint::new(0, 0), 0.)
1760// );
1761
1762// workspace
1763// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1764// .await
1765// .unwrap();
1766// assert_eq!(
1767// active_location(&workspace, cx),
1768// (file1.clone(), DisplayPoint::new(10, 0), 0.)
1769// );
1770
1771// workspace
1772// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1773// .await
1774// .unwrap();
1775// assert_eq!(
1776// active_location(&workspace, cx),
1777// (file1.clone(), DisplayPoint::new(0, 0), 0.)
1778// );
1779
1780// // Go back one more time and ensure we don't navigate past the first item in the history.
1781// workspace
1782// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1783// .await
1784// .unwrap();
1785// assert_eq!(
1786// active_location(&workspace, cx),
1787// (file1.clone(), DisplayPoint::new(0, 0), 0.)
1788// );
1789
1790// workspace
1791// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1792// .await
1793// .unwrap();
1794// assert_eq!(
1795// active_location(&workspace, cx),
1796// (file1.clone(), DisplayPoint::new(10, 0), 0.)
1797// );
1798
1799// workspace
1800// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1801// .await
1802// .unwrap();
1803// assert_eq!(
1804// active_location(&workspace, cx),
1805// (file2.clone(), DisplayPoint::new(0, 0), 0.)
1806// );
1807
1808// // Go forward to an item that has been closed, ensuring it gets re-opened at the same
1809// // location.
1810// pane.update(cx, |pane, cx| {
1811// let editor3_id = editor3.id();
1812// drop(editor3);
1813// pane.close_item_by_id(editor3_id, SaveIntent::Close, cx)
1814// })
1815// .await
1816// .unwrap();
1817// workspace
1818// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1819// .await
1820// .unwrap();
1821// assert_eq!(
1822// active_location(&workspace, cx),
1823// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1824// );
1825
1826// workspace
1827// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1828// .await
1829// .unwrap();
1830// assert_eq!(
1831// active_location(&workspace, cx),
1832// (file3.clone(), DisplayPoint::new(16, 0), 12.5)
1833// );
1834
1835// workspace
1836// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1837// .await
1838// .unwrap();
1839// assert_eq!(
1840// active_location(&workspace, cx),
1841// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1842// );
1843
1844// // Go back to an item that has been closed and removed from disk, ensuring it gets skipped.
1845// pane.update(cx, |pane, cx| {
1846// let editor2_id = editor2.id();
1847// drop(editor2);
1848// pane.close_item_by_id(editor2_id, SaveIntent::Close, cx)
1849// })
1850// .await
1851// .unwrap();
1852// app_state
1853// .fs
1854// .remove_file(Path::new("/root/a/file2"), Default::default())
1855// .await
1856// .unwrap();
1857// cx.foreground().run_until_parked();
1858
1859// workspace
1860// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1861// .await
1862// .unwrap();
1863// assert_eq!(
1864// active_location(&workspace, cx),
1865// (file1.clone(), DisplayPoint::new(10, 0), 0.)
1866// );
1867// workspace
1868// .update(cx, |w, cx| w.go_forward(w.active_pane().downgrade(), cx))
1869// .await
1870// .unwrap();
1871// assert_eq!(
1872// active_location(&workspace, cx),
1873// (file3.clone(), DisplayPoint::new(0, 0), 0.)
1874// );
1875
1876// // Modify file to collapse multiple nav history entries into the same location.
1877// // Ensure we don't visit the same location twice when navigating.
1878// editor1.update(cx, |editor, cx| {
1879// editor.change_selections(None, cx, |s| {
1880// s.select_display_ranges([DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)])
1881// })
1882// });
1883
1884// for _ in 0..5 {
1885// editor1.update(cx, |editor, cx| {
1886// editor.change_selections(None, cx, |s| {
1887// s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
1888// });
1889// });
1890// editor1.update(cx, |editor, cx| {
1891// editor.change_selections(None, cx, |s| {
1892// s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 0)])
1893// })
1894// });
1895// }
1896
1897// editor1.update(cx, |editor, cx| {
1898// editor.transact(cx, |editor, cx| {
1899// editor.change_selections(None, cx, |s| {
1900// s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(14, 0)])
1901// });
1902// editor.insert("", cx);
1903// })
1904// });
1905
1906// editor1.update(cx, |editor, cx| {
1907// editor.change_selections(None, cx, |s| {
1908// s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
1909// })
1910// });
1911// workspace
1912// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1913// .await
1914// .unwrap();
1915// assert_eq!(
1916// active_location(&workspace, cx),
1917// (file1.clone(), DisplayPoint::new(2, 0), 0.)
1918// );
1919// workspace
1920// .update(cx, |w, cx| w.go_back(w.active_pane().downgrade(), cx))
1921// .await
1922// .unwrap();
1923// assert_eq!(
1924// active_location(&workspace, cx),
1925// (file1.clone(), DisplayPoint::new(3, 0), 0.)
1926// );
1927
1928// fn active_location(
1929// workspace: &ViewHandle<Workspace>,
1930// cx: &mut TestAppContext,
1931// ) -> (ProjectPath, DisplayPoint, f32) {
1932// workspace.update(cx, |workspace, cx| {
1933// let item = workspace.active_item(cx).unwrap();
1934// let editor = item.downcast::<Editor>().unwrap();
1935// let (selections, scroll_position) = editor.update(cx, |editor, cx| {
1936// (
1937// editor.selections.display_ranges(cx),
1938// editor.scroll_position(cx),
1939// )
1940// });
1941// (
1942// item.project_path(cx).unwrap(),
1943// selections[0].start,
1944// scroll_position.y(),
1945// )
1946// })
1947// }
1948// }
1949
1950// #[gpui::test]
1951// async fn test_reopening_closed_items(cx: &mut TestAppContext) {
1952// let app_state = init_test(cx);
1953// app_state
1954// .fs
1955// .as_fake()
1956// .insert_tree(
1957// "/root",
1958// json!({
1959// "a": {
1960// "file1": "",
1961// "file2": "",
1962// "file3": "",
1963// "file4": "",
1964// },
1965// }),
1966// )
1967// .await;
1968
1969// let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
1970// let workspace = cx
1971// .add_window(|cx| Workspace::test_new(project, cx))
1972// .root(cx);
1973// let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
1974
1975// let entries = cx.read(|cx| workspace.file_project_paths(cx));
1976// let file1 = entries[0].clone();
1977// let file2 = entries[1].clone();
1978// let file3 = entries[2].clone();
1979// let file4 = entries[3].clone();
1980
1981// let file1_item_id = workspace
1982// .update(cx, |w, cx| w.open_path(file1.clone(), None, true, cx))
1983// .await
1984// .unwrap()
1985// .id();
1986// let file2_item_id = workspace
1987// .update(cx, |w, cx| w.open_path(file2.clone(), None, true, cx))
1988// .await
1989// .unwrap()
1990// .id();
1991// let file3_item_id = workspace
1992// .update(cx, |w, cx| w.open_path(file3.clone(), None, true, cx))
1993// .await
1994// .unwrap()
1995// .id();
1996// let file4_item_id = workspace
1997// .update(cx, |w, cx| w.open_path(file4.clone(), None, true, cx))
1998// .await
1999// .unwrap()
2000// .id();
2001// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2002
2003// // Close all the pane items in some arbitrary order.
2004// pane.update(cx, |pane, cx| {
2005// pane.close_item_by_id(file1_item_id, SaveIntent::Close, cx)
2006// })
2007// .await
2008// .unwrap();
2009// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2010
2011// pane.update(cx, |pane, cx| {
2012// pane.close_item_by_id(file4_item_id, SaveIntent::Close, cx)
2013// })
2014// .await
2015// .unwrap();
2016// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2017
2018// pane.update(cx, |pane, cx| {
2019// pane.close_item_by_id(file2_item_id, SaveIntent::Close, cx)
2020// })
2021// .await
2022// .unwrap();
2023// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2024
2025// pane.update(cx, |pane, cx| {
2026// pane.close_item_by_id(file3_item_id, SaveIntent::Close, cx)
2027// })
2028// .await
2029// .unwrap();
2030// assert_eq!(active_path(&workspace, cx), None);
2031
2032// // Reopen all the closed items, ensuring they are reopened in the same order
2033// // in which they were closed.
2034// workspace
2035// .update(cx, Workspace::reopen_closed_item)
2036// .await
2037// .unwrap();
2038// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2039
2040// workspace
2041// .update(cx, Workspace::reopen_closed_item)
2042// .await
2043// .unwrap();
2044// assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2045
2046// workspace
2047// .update(cx, Workspace::reopen_closed_item)
2048// .await
2049// .unwrap();
2050// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2051
2052// workspace
2053// .update(cx, Workspace::reopen_closed_item)
2054// .await
2055// .unwrap();
2056// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2057
2058// // Reopening past the last closed item is a no-op.
2059// workspace
2060// .update(cx, Workspace::reopen_closed_item)
2061// .await
2062// .unwrap();
2063// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2064
2065// // Reopening closed items doesn't interfere with navigation history.
2066// workspace
2067// .update(cx, |workspace, cx| {
2068// workspace.go_back(workspace.active_pane().downgrade(), cx)
2069// })
2070// .await
2071// .unwrap();
2072// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2073
2074// workspace
2075// .update(cx, |workspace, cx| {
2076// workspace.go_back(workspace.active_pane().downgrade(), cx)
2077// })
2078// .await
2079// .unwrap();
2080// assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2081
2082// workspace
2083// .update(cx, |workspace, cx| {
2084// workspace.go_back(workspace.active_pane().downgrade(), cx)
2085// })
2086// .await
2087// .unwrap();
2088// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2089
2090// workspace
2091// .update(cx, |workspace, cx| {
2092// workspace.go_back(workspace.active_pane().downgrade(), cx)
2093// })
2094// .await
2095// .unwrap();
2096// assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
2097
2098// workspace
2099// .update(cx, |workspace, cx| {
2100// workspace.go_back(workspace.active_pane().downgrade(), cx)
2101// })
2102// .await
2103// .unwrap();
2104// assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
2105
2106// workspace
2107// .update(cx, |workspace, cx| {
2108// workspace.go_back(workspace.active_pane().downgrade(), cx)
2109// })
2110// .await
2111// .unwrap();
2112// assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
2113
2114// workspace
2115// .update(cx, |workspace, cx| {
2116// workspace.go_back(workspace.active_pane().downgrade(), cx)
2117// })
2118// .await
2119// .unwrap();
2120// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2121
2122// workspace
2123// .update(cx, |workspace, cx| {
2124// workspace.go_back(workspace.active_pane().downgrade(), cx)
2125// })
2126// .await
2127// .unwrap();
2128// assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
2129
2130// fn active_path(
2131// workspace: &ViewHandle<Workspace>,
2132// cx: &TestAppContext,
2133// ) -> Option<ProjectPath> {
2134// workspace.read_with(cx, |workspace, cx| {
2135// let item = workspace.active_item(cx)?;
2136// item.project_path(cx)
2137// })
2138// }
2139// }
2140
2141// #[gpui::test]
2142// async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
2143// struct TestView;
2144
2145// impl Entity for TestView {
2146// type Event = ();
2147// }
2148
2149// impl View for TestView {
2150// fn ui_name() -> &'static str {
2151// "TestView"
2152// }
2153
2154// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2155// Empty::new().into_any()
2156// }
2157// }
2158
2159// let executor = cx.background();
2160// let fs = FakeFs::new(executor.clone());
2161
2162// actions!(test, [A, B]);
2163// // From the Atom keymap
2164// actions!(workspace, [ActivatePreviousPane]);
2165// // From the JetBrains keymap
2166// actions!(pane, [ActivatePrevItem]);
2167
2168// fs.save(
2169// "/settings.json".as_ref(),
2170// &r#"
2171// {
2172// "base_keymap": "Atom"
2173// }
2174// "#
2175// .into(),
2176// Default::default(),
2177// )
2178// .await
2179// .unwrap();
2180
2181// fs.save(
2182// "/keymap.json".as_ref(),
2183// &r#"
2184// [
2185// {
2186// "bindings": {
2187// "backspace": "test::A"
2188// }
2189// }
2190// ]
2191// "#
2192// .into(),
2193// Default::default(),
2194// )
2195// .await
2196// .unwrap();
2197
2198// cx.update(|cx| {
2199// cx.set_global(SettingsStore::test(cx));
2200// theme::init(Assets, cx);
2201// welcome::init(cx);
2202
2203// cx.add_global_action(|_: &A, _cx| {});
2204// cx.add_global_action(|_: &B, _cx| {});
2205// cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2206// cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2207
2208// let settings_rx = watch_config_file(
2209// executor.clone(),
2210// fs.clone(),
2211// PathBuf::from("/settings.json"),
2212// );
2213// let keymap_rx =
2214// watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2215
2216// handle_keymap_file_changes(keymap_rx, cx);
2217// handle_settings_file_changes(settings_rx, cx);
2218// });
2219
2220// cx.foreground().run_until_parked();
2221
2222// let window = cx.add_window(|_| TestView);
2223
2224// // Test loading the keymap base at all
2225// assert_key_bindings_for(
2226// window.into(),
2227// cx,
2228// vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2229// line!(),
2230// );
2231
2232// // Test modifying the users keymap, while retaining the base keymap
2233// fs.save(
2234// "/keymap.json".as_ref(),
2235// &r#"
2236// [
2237// {
2238// "bindings": {
2239// "backspace": "test::B"
2240// }
2241// }
2242// ]
2243// "#
2244// .into(),
2245// Default::default(),
2246// )
2247// .await
2248// .unwrap();
2249
2250// cx.foreground().run_until_parked();
2251
2252// assert_key_bindings_for(
2253// window.into(),
2254// cx,
2255// vec![("backspace", &B), ("k", &ActivatePreviousPane)],
2256// line!(),
2257// );
2258
2259// // Test modifying the base, while retaining the users keymap
2260// fs.save(
2261// "/settings.json".as_ref(),
2262// &r#"
2263// {
2264// "base_keymap": "JetBrains"
2265// }
2266// "#
2267// .into(),
2268// Default::default(),
2269// )
2270// .await
2271// .unwrap();
2272
2273// cx.foreground().run_until_parked();
2274
2275// assert_key_bindings_for(
2276// window.into(),
2277// cx,
2278// vec![("backspace", &B), ("[", &ActivatePrevItem)],
2279// line!(),
2280// );
2281
2282// #[track_caller]
2283// fn assert_key_bindings_for<'a>(
2284// window: AnyWindowHandle,
2285// cx: &TestAppContext,
2286// actions: Vec<(&'static str, &'a dyn Action)>,
2287// line: u32,
2288// ) {
2289// for (key, action) in actions {
2290// // assert that...
2291// assert!(
2292// cx.available_actions(window, 0)
2293// .into_iter()
2294// .any(|(_, bound_action, b)| {
2295// // action names match...
2296// bound_action.name() == action.name()
2297// && bound_action.namespace() == action.namespace()
2298// // and key strokes contain the given key
2299// && b.iter()
2300// .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2301// }),
2302// "On {} Failed to find {} with key binding {}",
2303// line,
2304// action.name(),
2305// key
2306// );
2307// }
2308// }
2309// }
2310
2311// #[gpui::test]
2312// async fn test_disabled_keymap_binding(cx: &mut gpui::TestAppContext) {
2313// struct TestView;
2314
2315// impl Entity for TestView {
2316// type Event = ();
2317// }
2318
2319// impl View for TestView {
2320// fn ui_name() -> &'static str {
2321// "TestView"
2322// }
2323
2324// fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
2325// Empty::new().into_any()
2326// }
2327// }
2328
2329// let executor = cx.background();
2330// let fs = FakeFs::new(executor.clone());
2331
2332// actions!(test, [A, B]);
2333// // From the Atom keymap
2334// actions!(workspace, [ActivatePreviousPane]);
2335// // From the JetBrains keymap
2336// actions!(pane, [ActivatePrevItem]);
2337
2338// fs.save(
2339// "/settings.json".as_ref(),
2340// &r#"
2341// {
2342// "base_keymap": "Atom"
2343// }
2344// "#
2345// .into(),
2346// Default::default(),
2347// )
2348// .await
2349// .unwrap();
2350
2351// fs.save(
2352// "/keymap.json".as_ref(),
2353// &r#"
2354// [
2355// {
2356// "bindings": {
2357// "backspace": "test::A"
2358// }
2359// }
2360// ]
2361// "#
2362// .into(),
2363// Default::default(),
2364// )
2365// .await
2366// .unwrap();
2367
2368// cx.update(|cx| {
2369// cx.set_global(SettingsStore::test(cx));
2370// theme::init(Assets, cx);
2371// welcome::init(cx);
2372
2373// cx.add_global_action(|_: &A, _cx| {});
2374// cx.add_global_action(|_: &B, _cx| {});
2375// cx.add_global_action(|_: &ActivatePreviousPane, _cx| {});
2376// cx.add_global_action(|_: &ActivatePrevItem, _cx| {});
2377
2378// let settings_rx = watch_config_file(
2379// executor.clone(),
2380// fs.clone(),
2381// PathBuf::from("/settings.json"),
2382// );
2383// let keymap_rx =
2384// watch_config_file(executor.clone(), fs.clone(), PathBuf::from("/keymap.json"));
2385
2386// handle_keymap_file_changes(keymap_rx, cx);
2387// handle_settings_file_changes(settings_rx, cx);
2388// });
2389
2390// cx.foreground().run_until_parked();
2391
2392// let window = cx.add_window(|_| TestView);
2393
2394// // Test loading the keymap base at all
2395// assert_key_bindings_for(
2396// window.into(),
2397// cx,
2398// vec![("backspace", &A), ("k", &ActivatePreviousPane)],
2399// line!(),
2400// );
2401
2402// // Test disabling the key binding for the base keymap
2403// fs.save(
2404// "/keymap.json".as_ref(),
2405// &r#"
2406// [
2407// {
2408// "bindings": {
2409// "backspace": null
2410// }
2411// }
2412// ]
2413// "#
2414// .into(),
2415// Default::default(),
2416// )
2417// .await
2418// .unwrap();
2419
2420// cx.foreground().run_until_parked();
2421
2422// assert_key_bindings_for(
2423// window.into(),
2424// cx,
2425// vec![("k", &ActivatePreviousPane)],
2426// line!(),
2427// );
2428
2429// // Test modifying the base, while retaining the users keymap
2430// fs.save(
2431// "/settings.json".as_ref(),
2432// &r#"
2433// {
2434// "base_keymap": "JetBrains"
2435// }
2436// "#
2437// .into(),
2438// Default::default(),
2439// )
2440// .await
2441// .unwrap();
2442
2443// cx.foreground().run_until_parked();
2444
2445// assert_key_bindings_for(window.into(), cx, vec![("[", &ActivatePrevItem)], line!());
2446
2447// #[track_caller]
2448// fn assert_key_bindings_for<'a>(
2449// window: AnyWindowHandle,
2450// cx: &TestAppContext,
2451// actions: Vec<(&'static str, &'a dyn Action)>,
2452// line: u32,
2453// ) {
2454// for (key, action) in actions {
2455// // assert that...
2456// assert!(
2457// cx.available_actions(window, 0)
2458// .into_iter()
2459// .any(|(_, bound_action, b)| {
2460// // action names match...
2461// bound_action.name() == action.name()
2462// && bound_action.namespace() == action.namespace()
2463// // and key strokes contain the given key
2464// && b.iter()
2465// .any(|binding| binding.keystrokes().iter().any(|k| k.key == key))
2466// }),
2467// "On {} Failed to find {} with key binding {}",
2468// line,
2469// action.name(),
2470// key
2471// );
2472// }
2473// }
2474// }
2475
2476// #[gpui::test]
2477// fn test_bundled_settings_and_themes(cx: &mut AppContext) {
2478// cx.platform()
2479// .fonts()
2480// .add_fonts(&[
2481// Assets
2482// .load("fonts/zed-sans/zed-sans-extended.ttf")
2483// .unwrap()
2484// .to_vec()
2485// .into(),
2486// Assets
2487// .load("fonts/zed-mono/zed-mono-extended.ttf")
2488// .unwrap()
2489// .to_vec()
2490// .into(),
2491// Assets
2492// .load("fonts/plex/IBMPlexSans-Regular.ttf")
2493// .unwrap()
2494// .to_vec()
2495// .into(),
2496// ])
2497// .unwrap();
2498// let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
2499// let mut settings = SettingsStore::default();
2500// settings
2501// .set_default_settings(&settings::default_settings(), cx)
2502// .unwrap();
2503// cx.set_global(settings);
2504// theme::init(Assets, cx);
2505
2506// let mut has_default_theme = false;
2507// for theme_name in themes.list(false).map(|meta| meta.name) {
2508// let theme = themes.get(&theme_name).unwrap();
2509// assert_eq!(theme.meta.name, theme_name);
2510// if theme.meta.name == settings::get::<ThemeSettings>(cx).theme.meta.name {
2511// has_default_theme = true;
2512// }
2513// }
2514// assert!(has_default_theme);
2515// }
2516
2517// #[gpui::test]
2518// fn test_bundled_languages(cx: &mut AppContext) {
2519// cx.set_global(SettingsStore::test(cx));
2520// let mut languages = LanguageRegistry::test();
2521// languages.set_executor(cx.background().clone());
2522// let languages = Arc::new(languages);
2523// let node_runtime = node_runtime::FakeNodeRuntime::new();
2524// languages::init(languages.clone(), node_runtime, cx);
2525// for name in languages.language_names() {
2526// languages.language_for_name(&name);
2527// }
2528// cx.foreground().run_until_parked();
2529// }
2530
2531// fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
2532// cx.foreground().forbid_parking();
2533// cx.update(|cx| {
2534// let mut app_state = AppState::test(cx);
2535// let state = Arc::get_mut(&mut app_state).unwrap();
2536// state.initialize_workspace = initialize_workspace;
2537// state.build_window_options = build_window_options;
2538// theme::init((), cx);
2539// audio::init((), cx);
2540// channel::init(&app_state.client, app_state.user_store.clone(), cx);
2541// call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2542// notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
2543// workspace::init(app_state.clone(), cx);
2544// Project::init_settings(cx);
2545// language::init(cx);
2546// editor::init(cx);
2547// project_panel::init_settings(cx);
2548// collab_ui::init(&app_state, cx);
2549// pane::init(cx);
2550// project_panel::init((), cx);
2551// terminal_view::init(cx);
2552// assistant::init(cx);
2553// app_state
2554// })
2555// }
2556
2557// fn rust_lang() -> Arc<language::Language> {
2558// Arc::new(language::Language::new(
2559// language::LanguageConfig {
2560// name: "Rust".into(),
2561// path_suffixes: vec!["rs".to_string()],
2562// ..Default::default()
2563// },
2564// Some(tree_sitter_rust::language()),
2565// ))
2566// }
2567// }