diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index f570f20bc5..5c835663f0 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -178,7 +178,7 @@ "focus": false } ], - "alt-\\": "copilot::NextSuggestion", + "alt-\\": "copilot::Suggest", "alt-]": "copilot::NextSuggestion", "alt-[": "copilot::PreviousSuggestion" } diff --git a/assets/settings/default.json b/assets/settings/default.json index 0f663e1b81..1ecfbf03a1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1,6 +1,11 @@ { // The name of the Zed theme to use for the UI "theme": "One Dark", + // Features that can be globally enabled or disabled + "features": { + // Show Copilot icon in status bar + "copilot": true + }, // The name of a font to use for rendering text in the editor "buffer_font_family": "Zed Mono", // The OpenType features to enable for text in the editor. @@ -13,11 +18,6 @@ // The factor to grow the active pane by. Defaults to 1.0 // which gives the same size as all other panes. "active_pane_magnification": 1.0, - // Enable / disable copilot integration. - "enable_copilot_integration": true, - // Controls whether copilot provides suggestion immediately - // or waits for a `copilot::Toggle` - "copilot": "on", // Whether to enable vim modes and key bindings "vim_mode": false, // Whether to show the informational hover box when moving the mouse @@ -30,6 +30,9 @@ // Whether to pop the completions menu while typing in an editor without // explicitly requesting it. "show_completions_on_input": true, + // Controls whether copilot provides suggestion immediately + // or waits for a `copilot::Toggle` + "show_copilot_suggestions": true, // Whether the screen sharing icon is shown in the os status bar. "show_call_status_icon": true, // Whether to use language servers to provide code intelligence. diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 43fcf55e95..1967c3cd14 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -29,7 +29,10 @@ const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth"; actions!(copilot_auth, [SignIn, SignOut]); const COPILOT_NAMESPACE: &'static str = "copilot"; -actions!(copilot, [NextSuggestion, PreviousSuggestion, Reinstall]); +actions!( + copilot, + [Suggest, NextSuggestion, PreviousSuggestion, Reinstall] +); pub fn init(http: Arc, node_runtime: Arc, cx: &mut AppContext) { // Disable Copilot for stable releases. @@ -172,7 +175,7 @@ impl Copilot { let http = http.clone(); let node_runtime = node_runtime.clone(); move |this, cx| { - if cx.global::().enable_copilot_integration { + if cx.global::().features.copilot { if matches!(this.server, CopilotServer::Disabled) { let start_task = cx .spawn({ @@ -194,12 +197,14 @@ impl Copilot { }) .detach(); - if cx.global::().enable_copilot_integration { + if cx.global::().features.copilot { let start_task = cx .spawn({ let http = http.clone(); let node_runtime = node_runtime.clone(); - move |this, cx| Self::start_language_server(http, node_runtime, this, cx) + move |this, cx| async { + Self::start_language_server(http, node_runtime, this, cx).await + } }) .shared(); diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index a65be325ce..dc09ddf3f2 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -2,12 +2,18 @@ use crate::{request::PromptUserDeviceFlow, Copilot, Status}; use gpui::{ elements::*, geometry::rect::RectF, + impl_internal_actions, platform::{WindowBounds, WindowKind, WindowOptions}, AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle, }; use settings::Settings; use theme::ui::modal; +#[derive(PartialEq, Eq, Debug, Clone)] +struct ClickedConnect; + +impl_internal_actions!(copilot_verification, [ClickedConnect]); + #[derive(PartialEq, Eq, Debug, Clone)] struct CopyUserCode; @@ -56,6 +62,12 @@ pub fn init(cx: &mut AppContext) { } }) .detach(); + + cx.add_action( + |code_verification: &mut CopilotCodeVerification, _: &ClickedConnect, _| { + code_verification.connect_clicked = true; + }, + ); } fn create_copilot_auth_window( @@ -81,11 +93,15 @@ fn create_copilot_auth_window( pub struct CopilotCodeVerification { status: Status, + connect_clicked: bool, } impl CopilotCodeVerification { pub fn new(status: Status) -> Self { - Self { status } + Self { + status, + connect_clicked: false, + } } pub fn set_status(&mut self, status: Status, cx: &mut ViewContext) { @@ -143,6 +159,7 @@ impl CopilotCodeVerification { } fn render_prompting_modal( + connect_clicked: bool, data: &PromptUserDeviceFlow, style: &theme::Copilot, cx: &mut gpui::RenderContext, @@ -189,13 +206,20 @@ impl CopilotCodeVerification { .with_style(style.auth.prompting.hint.container.clone()) .boxed(), theme::ui::cta_button_with_click( - "Connect to GitHub", + if connect_clicked { + "Waiting for connection..." + } else { + "Connect to GitHub" + }, style.auth.content_width, &style.auth.cta_button, cx, { let verification_uri = data.verification_uri.clone(); - move |_, cx| cx.platform().open_url(&verification_uri) + move |_, cx| { + cx.platform().open_url(&verification_uri); + cx.dispatch_action(ClickedConnect) + } }, ) .boxed(), @@ -343,9 +367,20 @@ impl View for CopilotCodeVerification { match &self.status { Status::SigningIn { prompt: Some(prompt), - } => Self::render_prompting_modal(&prompt, &style.copilot, cx), - Status::Unauthorized => Self::render_unauthorized_modal(&style.copilot, cx), - Status::Authorized => Self::render_enabled_modal(&style.copilot, cx), + } => Self::render_prompting_modal( + self.connect_clicked, + &prompt, + &style.copilot, + cx, + ), + Status::Unauthorized => { + self.connect_clicked = false; + Self::render_unauthorized_modal(&style.copilot, cx) + } + Status::Authorized => { + self.connect_clicked = false; + Self::render_enabled_modal(&style.copilot, cx) + } _ => Empty::new().boxed(), }, ]) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 8721829241..b7e93536b9 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -24,6 +24,15 @@ const COPILOT_ERROR_TOAST_ID: usize = 1338; #[derive(Clone, PartialEq)] pub struct DeployCopilotMenu; +#[derive(Clone, PartialEq)] +pub struct DeployCopilotStartMenu; + +#[derive(Clone, PartialEq)] +pub struct HideCopilot; + +#[derive(Clone, PartialEq)] +pub struct InitiateSignIn; + #[derive(Clone, PartialEq)] pub struct ToggleCopilotForLanguage { language: Arc, @@ -40,6 +49,9 @@ impl_internal_actions!( copilot, [ DeployCopilotMenu, + DeployCopilotStartMenu, + HideCopilot, + InitiateSignIn, DeployCopilotModal, ToggleCopilotForLanguage, ToggleCopilotGlobally, @@ -48,17 +60,19 @@ impl_internal_actions!( pub fn init(cx: &mut AppContext) { cx.add_action(CopilotButton::deploy_copilot_menu); + cx.add_action(CopilotButton::deploy_copilot_start_menu); cx.add_action( |_: &mut CopilotButton, action: &ToggleCopilotForLanguage, cx| { - let language = action.language.to_owned(); - - let current_langauge = cx.global::().copilot_on(Some(&language)); + let language = action.language.clone(); + let show_copilot_suggestions = cx + .global::() + .show_copilot_suggestions(Some(&language)); SettingsFile::update(cx, move |file_contents| { file_contents.languages.insert( - language.to_owned(), + language, settings::EditorSettings { - copilot: Some((!current_langauge).into()), + show_copilot_suggestions: Some((!show_copilot_suggestions).into()), ..Default::default() }, ); @@ -67,12 +81,63 @@ pub fn init(cx: &mut AppContext) { ); cx.add_action(|_: &mut CopilotButton, _: &ToggleCopilotGlobally, cx| { - let copilot_on = cx.global::().copilot_on(None); - + let show_copilot_suggestions = cx.global::().show_copilot_suggestions(None); SettingsFile::update(cx, move |file_contents| { - file_contents.editor.copilot = Some((!copilot_on).into()) + file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }) }); + + cx.add_action(|_: &mut CopilotButton, _: &HideCopilot, cx| { + SettingsFile::update(cx, move |file_contents| { + file_contents.features.copilot = Some(false) + }) + }); + + cx.add_action(|_: &mut CopilotButton, _: &InitiateSignIn, cx| { + let Some(copilot) = Copilot::global(cx) else { + return; + }; + let status = copilot.read(cx).status(); + + match status { + Status::Starting { task } => { + cx.dispatch_action(workspace::Toast::new( + COPILOT_STARTING_TOAST_ID, + "Copilot is starting...", + )); + let window_id = cx.window_id(); + let task = task.to_owned(); + cx.spawn(|handle, mut cx| async move { + task.await; + cx.update(|cx| { + if let Some(copilot) = Copilot::global(cx) { + let status = copilot.read(cx).status(); + match status { + Status::Authorized => cx.dispatch_action_at( + window_id, + handle.id(), + workspace::Toast::new( + COPILOT_STARTING_TOAST_ID, + "Copilot has started!", + ), + ), + _ => { + cx.dispatch_action_at( + window_id, + handle.id(), + DismissToast::new(COPILOT_STARTING_TOAST_ID), + ); + cx.dispatch_action_at(window_id, handle.id(), SignIn) + } + } + } + }) + }) + .detach(); + } + _ => cx.dispatch_action(SignIn), + } + }) } pub struct CopilotButton { @@ -94,7 +159,7 @@ impl View for CopilotButton { fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox { let settings = cx.global::(); - if !settings.enable_copilot_integration { + if !settings.features.copilot { return Empty::new().boxed(); } @@ -105,9 +170,9 @@ impl View for CopilotButton { }; let status = copilot.read(cx).status(); - let enabled = self.editor_enabled.unwrap_or(settings.copilot_on(None)); - - let view_id = cx.view_id(); + let enabled = self + .editor_enabled + .unwrap_or(settings.show_copilot_suggestions(None)); Stack::new() .with_child( @@ -155,48 +220,13 @@ impl View for CopilotButton { let status = status.clone(); move |_, cx| match status { Status::Authorized => cx.dispatch_action(DeployCopilotMenu), - Status::Starting { ref task } => { - cx.dispatch_action(workspace::Toast::new( - COPILOT_STARTING_TOAST_ID, - "Copilot is starting...", - )); - let window_id = cx.window_id(); - let task = task.to_owned(); - cx.spawn(|mut cx| async move { - task.await; - cx.update(|cx| { - if let Some(copilot) = Copilot::global(cx) { - let status = copilot.read(cx).status(); - match status { - Status::Authorized => cx.dispatch_action_at( - window_id, - view_id, - workspace::Toast::new( - COPILOT_STARTING_TOAST_ID, - "Copilot has started!", - ), - ), - _ => { - cx.dispatch_action_at( - window_id, - view_id, - DismissToast::new(COPILOT_STARTING_TOAST_ID), - ); - cx.dispatch_global_action(SignIn) - } - } - } - }) - }) - .detach(); - } Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action( COPILOT_ERROR_TOAST_ID, format!("Copilot can't be started: {}", e), "Reinstall Copilot", Reinstall, )), - _ => cx.dispatch_action(SignIn), + _ => cx.dispatch_action(DeployCopilotStartMenu), } }) .with_tooltip::( @@ -242,22 +272,38 @@ impl CopilotButton { } } + pub fn deploy_copilot_start_menu( + &mut self, + _: &DeployCopilotStartMenu, + cx: &mut ViewContext, + ) { + let mut menu_options = Vec::with_capacity(2); + + menu_options.push(ContextMenuItem::item("Sign In", InitiateSignIn)); + menu_options.push(ContextMenuItem::item("Hide Copilot", HideCopilot)); + + self.popup_menu.update(cx, |menu, cx| { + menu.show( + Default::default(), + AnchorCorner::BottomRight, + menu_options, + cx, + ); + }); + } + pub fn deploy_copilot_menu(&mut self, _: &DeployCopilotMenu, cx: &mut ViewContext) { let settings = cx.global::(); let mut menu_options = Vec::with_capacity(6); if let Some(language) = &self.language { - let language_enabled = settings.copilot_on(Some(language.as_ref())); + let language_enabled = settings.show_copilot_suggestions(Some(language.as_ref())); menu_options.push(ContextMenuItem::item( format!( - "{} Copilot for {}", - if language_enabled { - "Disable" - } else { - "Enable" - }, + "{} Suggestions for {}", + if language_enabled { "Hide" } else { "Show" }, language ), ToggleCopilotForLanguage { @@ -266,12 +312,12 @@ impl CopilotButton { )); } - let globally_enabled = cx.global::().copilot_on(None); + let globally_enabled = cx.global::().show_copilot_suggestions(None); menu_options.push(ContextMenuItem::item( if globally_enabled { - "Disable Copilot Globally" + "Hide Suggestions for All Files" } else { - "Enable Copilot Globally" + "Show Suggestions for All Files" }, ToggleCopilotGlobally, )); @@ -319,7 +365,7 @@ impl CopilotButton { self.language = language_name.clone(); - self.editor_enabled = Some(settings.copilot_on(language_name.as_deref())); + self.editor_enabled = Some(settings.show_copilot_suggestions(language_name.as_deref())); cx.notify() } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 48940c3075..f6fc89de2f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -397,6 +397,7 @@ pub fn init(cx: &mut AppContext) { cx.add_async_action(Editor::find_all_references); cx.add_action(Editor::next_copilot_suggestion); cx.add_action(Editor::previous_copilot_suggestion); + cx.add_action(Editor::copilot_suggest); hover_popover::init(cx); link_go_to_definition::init(cx); @@ -1016,6 +1017,8 @@ impl CodeActionsMenu { pub struct CopilotState { excerpt_id: Option, pending_refresh: Task>, + pending_cycling_refresh: Task>, + cycled: bool, completions: Vec, active_completion_index: usize, } @@ -1024,9 +1027,11 @@ impl Default for CopilotState { fn default() -> Self { Self { excerpt_id: None, + pending_cycling_refresh: Task::ready(Some(())), pending_refresh: Task::ready(Some(())), completions: Default::default(), active_completion_index: 0, + cycled: false, } } } @@ -1070,6 +1075,26 @@ impl CopilotState { } } + fn cycle_completions(&mut self, direction: Direction) { + match direction { + Direction::Prev => { + self.active_completion_index = if self.active_completion_index == 0 { + self.completions.len() - 1 + } else { + self.active_completion_index - 1 + }; + } + Direction::Next => { + if self.completions.len() == 0 { + self.active_completion_index = 0 + } else { + self.active_completion_index = + (self.active_completion_index + 1) % self.completions.len(); + } + } + } + } + fn push_completion(&mut self, new_completion: copilot::Completion) { for completion in &self.completions { if *completion == new_completion { @@ -1267,7 +1292,7 @@ impl Editor { cx.subscribe(&buffer, Self::on_buffer_event), cx.observe(&display_map, Self::on_display_map_changed), cx.observe(&blink_manager, |_, _, cx| cx.notify()), - cx.observe_global::(Self::on_settings_changed), + cx.observe_global::(Self::settings_changed), ], }; this.end_selection(cx); @@ -2028,13 +2053,13 @@ impl Editor { this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); if had_active_copilot_suggestion { - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); if !this.has_active_copilot_suggestion(cx) { this.trigger_completion_on_input(&text, cx); } } else { this.trigger_completion_on_input(&text, cx); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); } }); } @@ -2116,7 +2141,7 @@ impl Editor { .collect(); this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); } @@ -2591,7 +2616,7 @@ impl Editor { }); } - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); let project = self.project.clone()?; @@ -2884,10 +2909,14 @@ impl Editor { None } - fn refresh_copilot_suggestions(&mut self, cx: &mut ViewContext) -> Option<()> { + fn refresh_copilot_suggestions( + &mut self, + debounce: bool, + cx: &mut ViewContext, + ) -> Option<()> { let copilot = Copilot::global(cx)?; if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { - self.hide_copilot_suggestion(cx); + self.clear_copilot_suggestions(cx); return None; } self.update_visible_copilot_suggestion(cx); @@ -2895,28 +2924,35 @@ impl Editor { let snapshot = self.buffer.read(cx).snapshot(cx); let cursor = self.selections.newest_anchor().head(); let language_name = snapshot.language_at(cursor).map(|language| language.name()); - if !cx.global::().copilot_on(language_name.as_deref()) { - self.hide_copilot_suggestion(cx); + if !cx + .global::() + .show_copilot_suggestions(language_name.as_deref()) + { + self.clear_copilot_suggestions(cx); return None; } let (buffer, buffer_position) = self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; self.copilot_state.pending_refresh = cx.spawn_weak(|this, mut cx| async move { - cx.background().timer(COPILOT_DEBOUNCE_TIMEOUT).await; - let (completion, completions_cycling) = copilot.update(&mut cx, |copilot, cx| { - ( - copilot.completions(&buffer, buffer_position, cx), - copilot.completions_cycling(&buffer, buffer_position, cx), - ) - }); + if debounce { + cx.background().timer(COPILOT_DEBOUNCE_TIMEOUT).await; + } + + let completions = copilot + .update(&mut cx, |copilot, cx| { + copilot.completions(&buffer, buffer_position, cx) + }) + .await + .log_err() + .into_iter() + .flatten() + .collect_vec(); - let (completion, completions_cycling) = futures::join!(completion, completions_cycling); - let mut completions = Vec::new(); - completions.extend(completion.log_err().into_iter().flatten()); - completions.extend(completions_cycling.log_err().into_iter().flatten()); this.upgrade(&cx)?.update(&mut cx, |this, cx| { if !completions.is_empty() { + this.copilot_state.cycled = false; + this.copilot_state.pending_cycling_refresh = Task::ready(None); this.copilot_state.completions.clear(); this.copilot_state.active_completion_index = 0; this.copilot_state.excerpt_id = Some(cursor.excerpt_id); @@ -2933,34 +2969,73 @@ impl Editor { Some(()) } - fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext) { + fn cycle_suggestions( + &mut self, + direction: Direction, + cx: &mut ViewContext, + ) -> Option<()> { + let copilot = Copilot::global(cx)?; + if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { + return None; + } + + if self.copilot_state.cycled { + self.copilot_state.cycle_completions(direction); + self.update_visible_copilot_suggestion(cx); + } else { + let cursor = self.selections.newest_anchor().head(); + let (buffer, buffer_position) = + self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; + self.copilot_state.pending_cycling_refresh = cx.spawn_weak(|this, mut cx| async move { + let completions = copilot + .update(&mut cx, |copilot, cx| { + copilot.completions_cycling(&buffer, buffer_position, cx) + }) + .await; + + this.upgrade(&cx)?.update(&mut cx, |this, cx| { + this.copilot_state.cycled = true; + for completion in completions.log_err().into_iter().flatten() { + this.copilot_state.push_completion(completion); + } + this.copilot_state.cycle_completions(direction); + this.update_visible_copilot_suggestion(cx); + }); + + Some(()) + }); + } + + Some(()) + } + + fn copilot_suggest(&mut self, _: &copilot::Suggest, cx: &mut ViewContext) { if !self.has_active_copilot_suggestion(cx) { - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(false, cx); return; } - self.copilot_state.active_completion_index = - (self.copilot_state.active_completion_index + 1) % self.copilot_state.completions.len(); self.update_visible_copilot_suggestion(cx); } + fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext) { + if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Next, cx); + } else { + self.refresh_copilot_suggestions(false, cx); + } + } + fn previous_copilot_suggestion( &mut self, _: &copilot::PreviousSuggestion, cx: &mut ViewContext, ) { - if !self.has_active_copilot_suggestion(cx) { - self.refresh_copilot_suggestions(cx); - return; + if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Prev, cx); + } else { + self.refresh_copilot_suggestions(false, cx); } - - self.copilot_state.active_completion_index = - if self.copilot_state.active_completion_index == 0 { - self.copilot_state.completions.len() - 1 - } else { - self.copilot_state.active_completion_index - 1 - }; - self.update_visible_copilot_suggestion(cx); } fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { @@ -3002,11 +3077,11 @@ impl Editor { .copilot_state .text_for_active_completion(cursor, &snapshot) { - self.display_map.update(cx, |map, cx| { + self.display_map.update(cx, move |map, cx| { map.replace_suggestion( Some(Suggestion { position: cursor, - text: text.into(), + text: text.trim_end().into(), }), cx, ) @@ -3017,6 +3092,11 @@ impl Editor { } } + fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext) { + self.copilot_state = Default::default(); + self.hide_copilot_suggestion(cx); + } + pub fn render_code_actions_indicator( &self, style: &EditorStyle, @@ -3302,7 +3382,7 @@ impl Editor { this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); this.insert("", cx); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); } @@ -3318,7 +3398,7 @@ impl Editor { }) }); this.insert("", cx); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); } @@ -3414,7 +3494,7 @@ impl Editor { self.transact(cx, |this, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); } @@ -4094,7 +4174,7 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(cx); - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(true, cx); cx.emit(Event::Edited); } } @@ -4109,7 +4189,7 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(cx); - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(true, cx); cx.emit(Event::Edited); } } @@ -6570,8 +6650,8 @@ impl Editor { cx.notify(); } - fn on_settings_changed(&mut self, cx: &mut ViewContext) { - self.refresh_copilot_suggestions(cx); + fn settings_changed(&mut self, cx: &mut ViewContext) { + self.refresh_copilot_suggestions(true, cx); } pub fn set_searchable(&mut self, searchable: bool) { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index feb4017018..6942a6e57b 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -28,11 +28,11 @@ pub use watched_json::watch_files; #[derive(Clone)] pub struct Settings { + pub features: Features, pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, - pub enable_copilot_integration: bool, pub buffer_font_size: f32, pub active_pane_magnification: f32, pub cursor_blink: bool, @@ -177,43 +177,7 @@ pub struct EditorSettings { pub ensure_final_newline_on_save: Option, pub formatter: Option, pub enable_language_server: Option, - #[schemars(skip)] - pub copilot: Option, -} - -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum OnOff { - On, - Off, -} - -impl OnOff { - pub fn as_bool(&self) -> bool { - match self { - OnOff::On => true, - OnOff::Off => false, - } - } - - pub fn from_bool(value: bool) -> OnOff { - match value { - true => OnOff::On, - false => OnOff::Off, - } - } -} - -impl From for bool { - fn from(value: OnOff) -> bool { - value.as_bool() - } -} - -impl From for OnOff { - fn from(value: bool) -> OnOff { - OnOff::from_bool(value) - } + pub show_copilot_suggestions: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -437,8 +401,7 @@ pub struct SettingsFileContent { #[serde(default)] pub base_keymap: Option, #[serde(default)] - #[schemars(skip)] - pub enable_copilot_integration: Option, + pub features: FeaturesContent, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] @@ -447,6 +410,18 @@ pub struct LspSettings { pub initialization_options: Option, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Features { + pub copilot: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct FeaturesContent { + pub copilot: Option, +} + impl Settings { /// Fill out the settings corresponding to the default.json file, overrides will be set later pub fn defaults( @@ -500,7 +475,7 @@ impl Settings { format_on_save: required(defaults.editor.format_on_save), formatter: required(defaults.editor.formatter), enable_language_server: required(defaults.editor.enable_language_server), - copilot: required(defaults.editor.copilot), + show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions), }, editor_overrides: Default::default(), git: defaults.git.unwrap(), @@ -517,7 +492,9 @@ impl Settings { telemetry_overrides: Default::default(), auto_update: defaults.auto_update.unwrap(), base_keymap: Default::default(), - enable_copilot_integration: defaults.enable_copilot_integration.unwrap(), + features: Features { + copilot: defaults.features.copilot.unwrap(), + }, } } @@ -569,10 +546,7 @@ impl Settings { merge(&mut self.autosave, data.autosave); merge(&mut self.default_dock_anchor, data.default_dock_anchor); merge(&mut self.base_keymap, data.base_keymap); - merge( - &mut self.enable_copilot_integration, - data.enable_copilot_integration, - ); + merge(&mut self.features.copilot, data.features.copilot); self.editor_overrides = data.editor; self.git_overrides = data.git.unwrap_or_default(); @@ -596,12 +570,15 @@ impl Settings { self } - pub fn copilot_on(&self, language: Option<&str>) -> bool { - if self.enable_copilot_integration { - self.language_setting(language, |settings| settings.copilot.map(Into::into)) - } else { - false - } + pub fn features(&self) -> &Features { + &self.features + } + + pub fn show_copilot_suggestions(&self, language: Option<&str>) -> bool { + self.features.copilot + && self.language_setting(language, |settings| { + settings.show_copilot_suggestions.map(Into::into) + }) } pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 { @@ -740,7 +717,7 @@ impl Settings { format_on_save: Some(FormatOnSave::On), formatter: Some(Formatter::LanguageServer), enable_language_server: Some(true), - copilot: Some(OnOff::On), + show_copilot_suggestions: Some(true), }, editor_overrides: Default::default(), journal_defaults: Default::default(), @@ -760,7 +737,7 @@ impl Settings { telemetry_overrides: Default::default(), auto_update: true, base_keymap: Default::default(), - enable_copilot_integration: true, + features: Features { copilot: true }, } } @@ -1125,7 +1102,7 @@ mod tests { { "language_overrides": { "JSON": { - "copilot": "off" + "show_copilot_suggestions": false } } } @@ -1135,7 +1112,7 @@ mod tests { settings.languages.insert( "Rust".into(), EditorSettings { - copilot: Some(OnOff::On), + show_copilot_suggestions: Some(true), ..Default::default() }, ); @@ -1144,10 +1121,10 @@ mod tests { { "language_overrides": { "Rust": { - "copilot": "on" + "show_copilot_suggestions": true }, "JSON": { - "copilot": "off" + "show_copilot_suggestions": false } } } @@ -1163,21 +1140,21 @@ mod tests { { "languages": { "JSON": { - "copilot": "off" + "show_copilot_suggestions": false } } } "# .unindent(), |settings| { - settings.editor.copilot = Some(OnOff::On); + settings.editor.show_copilot_suggestions = Some(true); }, r#" { - "copilot": "on", + "show_copilot_suggestions": true, "languages": { "JSON": { - "copilot": "off" + "show_copilot_suggestions": false } } } @@ -1187,13 +1164,13 @@ mod tests { } #[test] - fn test_update_langauge_copilot() { + fn test_update_language_copilot() { assert_new_settings( r#" { "languages": { "JSON": { - "copilot": "off" + "show_copilot_suggestions": false } } } @@ -1203,7 +1180,7 @@ mod tests { settings.languages.insert( "Rust".into(), EditorSettings { - copilot: Some(OnOff::On), + show_copilot_suggestions: Some(true), ..Default::default() }, ); @@ -1212,10 +1189,10 @@ mod tests { { "languages": { "Rust": { - "copilot": "on" + "show_copilot_suggestions": true }, "JSON": { - "copilot": "off" + "show_copilot_suggestions": false } } } diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 0c2435ed5a..84ef51406e 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -44,9 +44,7 @@ export default function editor(colorScheme: ColorScheme) { activeLineBackground: withOpacity(background(layer, "on"), 0.75), highlightedLineBackground: background(layer, "on"), // Inline autocomplete suggestions, Co-pilot suggestions, etc. - suggestion: { - color: syntax.predictive.color, - }, + suggestion: syntax.predictive, codeActions: { indicator: { color: foreground(layer, "variant"), diff --git a/styles/src/themes/common/syntax.ts b/styles/src/themes/common/syntax.ts index 50c3e4e3b7..925ed7e5c1 100644 --- a/styles/src/themes/common/syntax.ts +++ b/styles/src/themes/common/syntax.ts @@ -181,6 +181,7 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { }, predictive: { color: color.predictive, + italic: true, }, emphasis: { color: color.emphasis,