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