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