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