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