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