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