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