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