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