1use anyhow::Result;
2use client::UserStore;
3use copilot::{Copilot, Status};
4use editor::{
5 actions::{ShowEditPrediction, ToggleEditPrediction},
6 scroll::Autoscroll,
7 Editor,
8};
9use feature_flags::{
10 FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
11};
12use fs::Fs;
13use gpui::{
14 actions, div, pulsating_between, Action, Animation, AnimationExt, App, AsyncWindowContext,
15 Corner, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render, Subscription,
16 WeakEntity,
17};
18use indoc::indoc;
19use language::{
20 language_settings::{self, all_language_settings, AllLanguageSettings, EditPredictionProvider},
21 File, Language,
22};
23use regex::Regex;
24use settings::{update_settings_file, Settings, SettingsStore};
25use std::{
26 sync::{Arc, LazyLock},
27 time::Duration,
28};
29use supermaven::{AccountStatus, Supermaven};
30use ui::{
31 prelude::*, Clickable, ContextMenu, ContextMenuEntry, IconButton, IconButtonShape, Indicator,
32 PopoverMenu, PopoverMenuHandle, Tooltip,
33};
34use workspace::{
35 create_and_open_local_file, item::ItemHandle, notifications::NotificationId, StatusItemView,
36 Toast, Workspace,
37};
38use zed_actions::OpenBrowser;
39use zeta::RateCompletionModal;
40
41actions!(zeta, [RateCompletions]);
42actions!(edit_prediction, [ToggleMenu]);
43
44const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
45
46struct CopilotErrorToast;
47
48pub struct InlineCompletionButton {
49 editor_subscription: Option<(Subscription, usize)>,
50 editor_enabled: Option<bool>,
51 editor_show_predictions: bool,
52 editor_focus_handle: Option<FocusHandle>,
53 language: Option<Arc<Language>>,
54 file: Option<Arc<dyn File>>,
55 edit_prediction_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
56 fs: Arc<dyn Fs>,
57 workspace: WeakEntity<Workspace>,
58 user_store: Entity<UserStore>,
59 popover_menu_handle: PopoverMenuHandle<ContextMenu>,
60}
61
62enum SupermavenButtonStatus {
63 Ready,
64 Errored(String),
65 NeedsActivation(String),
66 Initializing,
67}
68
69impl Render for InlineCompletionButton {
70 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
71 let all_language_settings = all_language_settings(None, cx);
72
73 match all_language_settings.edit_predictions.provider {
74 EditPredictionProvider::None => div(),
75
76 EditPredictionProvider::Copilot => {
77 let Some(copilot) = Copilot::global(cx) else {
78 return div();
79 };
80 let status = copilot.read(cx).status();
81
82 let enabled = self.editor_enabled.unwrap_or(false);
83
84 let icon = match status {
85 Status::Error(_) => IconName::CopilotError,
86 Status::Authorized => {
87 if enabled {
88 IconName::Copilot
89 } else {
90 IconName::CopilotDisabled
91 }
92 }
93 _ => IconName::CopilotInit,
94 };
95
96 if let Status::Error(e) = status {
97 return div().child(
98 IconButton::new("copilot-error", icon)
99 .icon_size(IconSize::Small)
100 .on_click(cx.listener(move |_, _, window, cx| {
101 if let Some(workspace) = window.root::<Workspace>().flatten() {
102 workspace.update(cx, |workspace, cx| {
103 workspace.show_toast(
104 Toast::new(
105 NotificationId::unique::<CopilotErrorToast>(),
106 format!("Copilot can't be started: {}", e),
107 )
108 .on_click(
109 "Reinstall Copilot",
110 |_, cx| {
111 if let Some(copilot) = Copilot::global(cx) {
112 copilot
113 .update(cx, |copilot, cx| {
114 copilot.reinstall(cx)
115 })
116 .detach();
117 }
118 },
119 ),
120 cx,
121 );
122 });
123 }
124 }))
125 .tooltip(|window, cx| {
126 Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
127 }),
128 );
129 }
130 let this = cx.entity().clone();
131
132 div().child(
133 PopoverMenu::new("copilot")
134 .menu(move |window, cx| {
135 Some(match status {
136 Status::Authorized => this.update(cx, |this, cx| {
137 this.build_copilot_context_menu(window, cx)
138 }),
139 _ => this.update(cx, |this, cx| {
140 this.build_copilot_start_menu(window, cx)
141 }),
142 })
143 })
144 .anchor(Corner::BottomRight)
145 .trigger_with_tooltip(
146 IconButton::new("copilot-icon", icon),
147 |window, cx| {
148 Tooltip::for_action("GitHub Copilot", &ToggleMenu, window, cx)
149 },
150 )
151 .with_handle(self.popover_menu_handle.clone()),
152 )
153 }
154
155 EditPredictionProvider::Supermaven => {
156 let Some(supermaven) = Supermaven::global(cx) else {
157 return div();
158 };
159
160 let supermaven = supermaven.read(cx);
161
162 let status = match supermaven {
163 Supermaven::Starting => SupermavenButtonStatus::Initializing,
164 Supermaven::FailedDownload { error } => {
165 SupermavenButtonStatus::Errored(error.to_string())
166 }
167 Supermaven::Spawned(agent) => {
168 let account_status = agent.account_status.clone();
169 match account_status {
170 AccountStatus::NeedsActivation { activate_url } => {
171 SupermavenButtonStatus::NeedsActivation(activate_url.clone())
172 }
173 AccountStatus::Unknown => SupermavenButtonStatus::Initializing,
174 AccountStatus::Ready => SupermavenButtonStatus::Ready,
175 }
176 }
177 Supermaven::Error { error } => {
178 SupermavenButtonStatus::Errored(error.to_string())
179 }
180 };
181
182 let icon = status.to_icon();
183 let tooltip_text = status.to_tooltip();
184 let has_menu = status.has_menu();
185 let this = cx.entity().clone();
186 let fs = self.fs.clone();
187
188 return div().child(
189 PopoverMenu::new("supermaven")
190 .menu(move |window, cx| match &status {
191 SupermavenButtonStatus::NeedsActivation(activate_url) => {
192 Some(ContextMenu::build(window, cx, |menu, _, _| {
193 let fs = fs.clone();
194 let activate_url = activate_url.clone();
195 menu.entry("Sign In", None, move |_, cx| {
196 cx.open_url(activate_url.as_str())
197 })
198 .entry(
199 "Use Copilot",
200 None,
201 move |_, cx| {
202 set_completion_provider(
203 fs.clone(),
204 cx,
205 EditPredictionProvider::Copilot,
206 )
207 },
208 )
209 }))
210 }
211 SupermavenButtonStatus::Ready => Some(this.update(cx, |this, cx| {
212 this.build_supermaven_context_menu(window, cx)
213 })),
214 _ => None,
215 })
216 .anchor(Corner::BottomRight)
217 .trigger_with_tooltip(
218 IconButton::new("supermaven-icon", icon),
219 move |window, cx| {
220 if has_menu {
221 Tooltip::for_action(
222 tooltip_text.clone(),
223 &ToggleMenu,
224 window,
225 cx,
226 )
227 } else {
228 Tooltip::text(tooltip_text.clone())(window, cx)
229 }
230 },
231 )
232 .with_handle(self.popover_menu_handle.clone()),
233 );
234 }
235
236 EditPredictionProvider::Zed => {
237 if !cx.has_flag::<PredictEditsFeatureFlag>() {
238 return div();
239 }
240
241 let enabled = self.editor_enabled.unwrap_or(true);
242
243 let zeta_icon = if enabled {
244 IconName::ZedPredict
245 } else {
246 IconName::ZedPredictDisabled
247 };
248
249 let current_user_terms_accepted =
250 self.user_store.read(cx).current_user_has_accepted_terms();
251
252 if !current_user_terms_accepted.unwrap_or(false) {
253 let signed_in = current_user_terms_accepted.is_some();
254 let tooltip_meta = if signed_in {
255 "Read Terms of Service"
256 } else {
257 "Sign in to use"
258 };
259
260 return div().child(
261 IconButton::new("zed-predict-pending-button", zeta_icon)
262 .shape(IconButtonShape::Square)
263 .indicator(Indicator::dot().color(Color::Error))
264 .indicator_border_color(Some(cx.theme().colors().status_bar_background))
265 .tooltip(move |window, cx| {
266 Tooltip::with_meta(
267 "Edit Predictions",
268 None,
269 tooltip_meta,
270 window,
271 cx,
272 )
273 })
274 .on_click(cx.listener(move |_, _, window, cx| {
275 telemetry::event!(
276 "Pending ToS Clicked",
277 source = "Edit Prediction Status Button"
278 );
279 window.dispatch_action(
280 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
281 cx,
282 );
283 })),
284 );
285 }
286
287 let show_editor_predictions = self.editor_show_predictions;
288
289 let icon_button = IconButton::new("zed-predict-pending-button", zeta_icon)
290 .shape(IconButtonShape::Square)
291 .when(enabled && !show_editor_predictions, |this| {
292 this.indicator(Indicator::dot().color(Color::Muted))
293 .indicator_border_color(Some(cx.theme().colors().status_bar_background))
294 })
295 .when(!self.popover_menu_handle.is_deployed(), |element| {
296 element.tooltip(move |window, cx| {
297 if enabled {
298 if show_editor_predictions {
299 Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
300 } else {
301 Tooltip::with_meta(
302 "Edit Prediction",
303 Some(&ToggleMenu),
304 "Hidden For This File",
305 window,
306 cx,
307 )
308 }
309 } else {
310 Tooltip::with_meta(
311 "Edit Prediction",
312 Some(&ToggleMenu),
313 "Disabled For This File",
314 window,
315 cx,
316 )
317 }
318 })
319 });
320
321 let this = cx.entity().clone();
322
323 let mut popover_menu = PopoverMenu::new("zeta")
324 .menu(move |window, cx| {
325 Some(this.update(cx, |this, cx| this.build_zeta_context_menu(window, cx)))
326 })
327 .anchor(Corner::BottomRight)
328 .with_handle(self.popover_menu_handle.clone());
329
330 let is_refreshing = self
331 .edit_prediction_provider
332 .as_ref()
333 .map_or(false, |provider| provider.is_refreshing(cx));
334
335 if is_refreshing {
336 popover_menu = popover_menu.trigger(
337 icon_button.with_animation(
338 "pulsating-label",
339 Animation::new(Duration::from_secs(2))
340 .repeat()
341 .with_easing(pulsating_between(0.2, 1.0)),
342 |icon_button, delta| icon_button.alpha(delta),
343 ),
344 );
345 } else {
346 popover_menu = popover_menu.trigger(icon_button);
347 }
348
349 div().child(popover_menu.into_any_element())
350 }
351 }
352 }
353}
354
355impl InlineCompletionButton {
356 pub fn new(
357 workspace: WeakEntity<Workspace>,
358 fs: Arc<dyn Fs>,
359 user_store: Entity<UserStore>,
360 popover_menu_handle: PopoverMenuHandle<ContextMenu>,
361 cx: &mut Context<Self>,
362 ) -> Self {
363 if let Some(copilot) = Copilot::global(cx) {
364 cx.observe(&copilot, |_, _, cx| cx.notify()).detach()
365 }
366
367 cx.observe_global::<SettingsStore>(move |_, cx| cx.notify())
368 .detach();
369
370 Self {
371 editor_subscription: None,
372 editor_enabled: None,
373 editor_show_predictions: true,
374 editor_focus_handle: None,
375 language: None,
376 file: None,
377 edit_prediction_provider: None,
378 popover_menu_handle,
379 workspace,
380 fs,
381 user_store,
382 }
383 }
384
385 pub fn build_copilot_start_menu(
386 &mut self,
387 window: &mut Window,
388 cx: &mut Context<Self>,
389 ) -> Entity<ContextMenu> {
390 let fs = self.fs.clone();
391 ContextMenu::build(window, cx, |menu, _, _| {
392 menu.entry("Sign In", None, copilot::initiate_sign_in)
393 .entry("Disable Copilot", None, {
394 let fs = fs.clone();
395 move |_window, cx| hide_copilot(fs.clone(), cx)
396 })
397 .entry("Use Supermaven", None, {
398 let fs = fs.clone();
399 move |_window, cx| {
400 set_completion_provider(fs.clone(), cx, EditPredictionProvider::Supermaven)
401 }
402 })
403 })
404 }
405
406 pub fn build_language_settings_menu(&self, mut menu: ContextMenu, cx: &mut App) -> ContextMenu {
407 let fs = self.fs.clone();
408
409 menu = menu.header("Show Edit Predictions For");
410
411 if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
412 menu = menu.toggleable_entry(
413 "This File",
414 self.editor_show_predictions,
415 IconPosition::Start,
416 Some(Box::new(ToggleEditPrediction)),
417 {
418 let editor_focus_handle = editor_focus_handle.clone();
419 move |window, cx| {
420 editor_focus_handle.dispatch_action(&ToggleEditPrediction, window, cx);
421 }
422 },
423 );
424 }
425
426 if let Some(language) = self.language.clone() {
427 let fs = fs.clone();
428 let language_enabled =
429 language_settings::language_settings(Some(language.name()), None, cx)
430 .show_edit_predictions;
431
432 menu = menu.toggleable_entry(
433 language.name(),
434 language_enabled,
435 IconPosition::Start,
436 None,
437 move |_, cx| {
438 toggle_show_inline_completions_for_language(language.clone(), fs.clone(), cx)
439 },
440 );
441 }
442
443 let settings = AllLanguageSettings::get_global(cx);
444 let globally_enabled = settings.show_inline_completions(None, cx);
445 menu = menu.toggleable_entry(
446 "All Files",
447 globally_enabled,
448 IconPosition::Start,
449 None,
450 move |_, cx| toggle_inline_completions_globally(fs.clone(), cx),
451 );
452 menu = menu.separator().header("Privacy Settings");
453
454 if let Some(provider) = &self.edit_prediction_provider {
455 let data_collection = provider.data_collection_state(cx);
456 if data_collection.is_supported() {
457 let provider = provider.clone();
458 let enabled = data_collection.is_enabled();
459 let is_open_source = data_collection.is_project_open_source();
460 let is_collecting = data_collection.is_enabled();
461
462 menu = menu.item(
463 ContextMenuEntry::new("Share Training Data")
464 .toggleable(IconPosition::Start, data_collection.is_enabled())
465 .icon_color(if is_open_source && is_collecting {
466 Color::Success
467 } else {
468 Color::Accent
469 })
470 .documentation_aside(move |cx| {
471 let (msg, label_color, icon_name, icon_color) = match (is_open_source, is_collecting) {
472 (true, true) => (
473 "Project identified as open-source, and you're sharing data.",
474 Color::Default,
475 IconName::Check,
476 Color::Success,
477 ),
478 (true, false) => (
479 "Project identified as open-source, but you're not sharing data.",
480 Color::Muted,
481 IconName::XCircle,
482 Color::Muted,
483 ),
484 (false, _) => (
485 "Project not identified as open-source. No data captured.",
486 Color::Muted,
487 IconName::XCircle,
488 Color::Muted,
489 ),
490 };
491 v_flex()
492 .gap_2()
493 .child(
494 Label::new(indoc!{
495 "Help us improve our open model by sharing data from open source repositories. \
496 Zed must detect a license file in your repo for this setting to take effect."
497 })
498 )
499 .child(
500 h_flex()
501 .pt_2()
502 .gap_1p5()
503 .border_t_1()
504 .border_color(cx.theme().colors().border_variant)
505 .child(Icon::new(icon_name).size(IconSize::XSmall).color(icon_color))
506 .child(div().child(Label::new(msg).size(LabelSize::Small).color(label_color)))
507 )
508 .into_any_element()
509 })
510 .handler(move |_, cx| {
511 provider.toggle_data_collection(cx);
512
513 if !enabled {
514 telemetry::event!(
515 "Data Collection Enabled",
516 source = "Edit Prediction Status Menu"
517 );
518 } else {
519 telemetry::event!(
520 "Data Collection Disabled",
521 source = "Edit Prediction Status Menu"
522 );
523 }
524 })
525 );
526 }
527 }
528
529 menu = menu.item(
530 ContextMenuEntry::new("Configure Excluded Files")
531 .icon(IconName::LockOutlined)
532 .icon_color(Color::Muted)
533 .documentation_aside(|_| {
534 Label::new(indoc!{"
535 Open your settings to add sensitive paths for which Zed will never predict edits."}).into_any_element()
536 })
537 .handler(move |window, cx| {
538 if let Some(workspace) = window.root().flatten() {
539 let workspace = workspace.downgrade();
540 window
541 .spawn(cx, |cx| {
542 open_disabled_globs_setting_in_editor(
543 workspace,
544 cx,
545 )
546 })
547 .detach_and_log_err(cx);
548 }
549 }),
550 );
551
552 if !self.editor_enabled.unwrap_or(true) {
553 menu = menu.item(
554 ContextMenuEntry::new("This file is excluded.")
555 .disabled(true)
556 .icon(IconName::ZedPredictDisabled)
557 .icon_size(IconSize::Small),
558 );
559 }
560
561 if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
562 menu = menu
563 .separator()
564 .entry(
565 "Predict Edit at Cursor",
566 Some(Box::new(ShowEditPrediction)),
567 {
568 let editor_focus_handle = editor_focus_handle.clone();
569 move |window, cx| {
570 editor_focus_handle.dispatch_action(&ShowEditPrediction, window, cx);
571 }
572 },
573 )
574 .context(editor_focus_handle);
575 }
576
577 menu
578 }
579
580 fn build_copilot_context_menu(
581 &self,
582 window: &mut Window,
583 cx: &mut Context<Self>,
584 ) -> Entity<ContextMenu> {
585 ContextMenu::build(window, cx, |menu, _, cx| {
586 self.build_language_settings_menu(menu, cx)
587 .separator()
588 .link(
589 "Go to Copilot Settings",
590 OpenBrowser {
591 url: COPILOT_SETTINGS_URL.to_string(),
592 }
593 .boxed_clone(),
594 )
595 .action("Sign Out", copilot::SignOut.boxed_clone())
596 })
597 }
598
599 fn build_supermaven_context_menu(
600 &self,
601 window: &mut Window,
602 cx: &mut Context<Self>,
603 ) -> Entity<ContextMenu> {
604 ContextMenu::build(window, cx, |menu, _, cx| {
605 self.build_language_settings_menu(menu, cx)
606 .separator()
607 .action("Sign Out", supermaven::SignOut.boxed_clone())
608 })
609 }
610
611 fn build_zeta_context_menu(
612 &self,
613 window: &mut Window,
614 cx: &mut Context<Self>,
615 ) -> Entity<ContextMenu> {
616 let workspace = self.workspace.clone();
617 ContextMenu::build(window, cx, |menu, _window, cx| {
618 self.build_language_settings_menu(menu, cx).when(
619 cx.has_flag::<PredictEditsRateCompletionsFeatureFlag>(),
620 |this| {
621 this.entry(
622 "Rate Completions",
623 Some(RateCompletions.boxed_clone()),
624 move |window, cx| {
625 workspace
626 .update(cx, |workspace, cx| {
627 RateCompletionModal::toggle(workspace, window, cx)
628 })
629 .ok();
630 },
631 )
632 },
633 )
634 })
635 }
636
637 pub fn update_enabled(&mut self, editor: Entity<Editor>, cx: &mut Context<Self>) {
638 let editor = editor.read(cx);
639 let snapshot = editor.buffer().read(cx).snapshot(cx);
640 let suggestion_anchor = editor.selections.newest_anchor().start;
641 let language = snapshot.language_at(suggestion_anchor);
642 let file = snapshot.file_at(suggestion_anchor).cloned();
643 self.editor_enabled = {
644 let file = file.as_ref();
645 Some(
646 file.map(|file| {
647 all_language_settings(Some(file), cx)
648 .inline_completions_enabled_for_path(file.path())
649 })
650 .unwrap_or(true),
651 )
652 };
653 self.editor_show_predictions = editor.edit_predictions_enabled();
654 self.edit_prediction_provider = editor.edit_prediction_provider();
655 self.language = language.cloned();
656 self.file = file;
657 self.editor_focus_handle = Some(editor.focus_handle(cx));
658
659 cx.notify();
660 }
661
662 pub fn toggle_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {
663 self.popover_menu_handle.toggle(window, cx);
664 }
665}
666
667impl StatusItemView for InlineCompletionButton {
668 fn set_active_pane_item(
669 &mut self,
670 item: Option<&dyn ItemHandle>,
671 _: &mut Window,
672 cx: &mut Context<Self>,
673 ) {
674 if let Some(editor) = item.and_then(|item| item.act_as::<Editor>(cx)) {
675 self.editor_subscription = Some((
676 cx.observe(&editor, Self::update_enabled),
677 editor.entity_id().as_u64() as usize,
678 ));
679 self.update_enabled(editor, cx);
680 } else {
681 self.language = None;
682 self.editor_subscription = None;
683 self.editor_enabled = None;
684 }
685 cx.notify();
686 }
687}
688
689impl SupermavenButtonStatus {
690 fn to_icon(&self) -> IconName {
691 match self {
692 SupermavenButtonStatus::Ready => IconName::Supermaven,
693 SupermavenButtonStatus::Errored(_) => IconName::SupermavenError,
694 SupermavenButtonStatus::NeedsActivation(_) => IconName::SupermavenInit,
695 SupermavenButtonStatus::Initializing => IconName::SupermavenInit,
696 }
697 }
698
699 fn to_tooltip(&self) -> String {
700 match self {
701 SupermavenButtonStatus::Ready => "Supermaven is ready".to_string(),
702 SupermavenButtonStatus::Errored(error) => format!("Supermaven error: {}", error),
703 SupermavenButtonStatus::NeedsActivation(_) => "Supermaven needs activation".to_string(),
704 SupermavenButtonStatus::Initializing => "Supermaven initializing".to_string(),
705 }
706 }
707
708 fn has_menu(&self) -> bool {
709 match self {
710 SupermavenButtonStatus::Ready | SupermavenButtonStatus::NeedsActivation(_) => true,
711 SupermavenButtonStatus::Errored(_) | SupermavenButtonStatus::Initializing => false,
712 }
713 }
714}
715
716async fn open_disabled_globs_setting_in_editor(
717 workspace: WeakEntity<Workspace>,
718 mut cx: AsyncWindowContext,
719) -> Result<()> {
720 let settings_editor = workspace
721 .update_in(&mut cx, |_, window, cx| {
722 create_and_open_local_file(paths::settings_file(), window, cx, || {
723 settings::initial_user_settings_content().as_ref().into()
724 })
725 })?
726 .await?
727 .downcast::<Editor>()
728 .unwrap();
729
730 settings_editor
731 .downgrade()
732 .update_in(&mut cx, |item, window, cx| {
733 let text = item.buffer().read(cx).snapshot(cx).text();
734
735 let settings = cx.global::<SettingsStore>();
736
737 // Ensure that we always have "inline_completions { "disabled_globs": [] }"
738 let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
739 file.edit_predictions
740 .get_or_insert_with(Default::default)
741 .disabled_globs
742 .get_or_insert_with(Vec::new);
743 });
744
745 if !edits.is_empty() {
746 item.edit(edits.iter().cloned(), cx);
747 }
748
749 let text = item.buffer().read(cx).snapshot(cx).text();
750
751 static DISABLED_GLOBS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
752 Regex::new(r#""disabled_globs":\s*\[\s*(?P<content>(?:.|\n)*?)\s*\]"#).unwrap()
753 });
754 // Only capture [...]
755 let range = DISABLED_GLOBS_REGEX.captures(&text).and_then(|captures| {
756 captures
757 .name("content")
758 .map(|inner_match| inner_match.start()..inner_match.end())
759 });
760 if let Some(range) = range {
761 item.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
762 selections.select_ranges(vec![range]);
763 });
764 }
765 })?;
766
767 anyhow::Ok(())
768}
769
770fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
771 let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx);
772 update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
773 file.defaults.show_edit_predictions = Some(!show_edit_predictions)
774 });
775}
776
777fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPredictionProvider) {
778 update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
779 file.features
780 .get_or_insert(Default::default())
781 .edit_prediction_provider = Some(provider);
782 });
783}
784
785fn toggle_show_inline_completions_for_language(
786 language: Arc<Language>,
787 fs: Arc<dyn Fs>,
788 cx: &mut App,
789) {
790 let show_edit_predictions =
791 all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
792 update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
793 file.languages
794 .entry(language.name())
795 .or_default()
796 .show_edit_predictions = Some(!show_edit_predictions);
797 });
798}
799
800fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut App) {
801 update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
802 file.features
803 .get_or_insert(Default::default())
804 .edit_prediction_provider = Some(EditPredictionProvider::None);
805 });
806}