From 1fd07b6fcfc455c40ce30c474690924eed89198f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Apr 2023 20:07:05 +0200 Subject: [PATCH 01/11] Clarify copilot settings Co-Authored-By: Nathan Sobo --- assets/settings/default.json | 13 ++- crates/copilot/src/copilot.rs | 4 +- crates/copilot_button/src/copilot_button.rs | 28 ++--- crates/editor/src/editor.rs | 5 +- crates/settings/src/settings.rs | 111 ++++++++------------ 5 files changed, 73 insertions(+), 88 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 0f663e1b8148eda2c8762957ca5738ba6f0dfea2..1ecfbf03a1c4ec801a008371427b8f55f77b09e5 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 43fcf55e958e7ca2c05cd350df3dc4fcb5fa2c77..aa086775b3b0ef1652de8b72554fcc591faa55fb 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -172,7 +172,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,7 +194,7 @@ impl Copilot { }) .detach(); - if cx.global::().enable_copilot_integration { + if cx.global::().features.copilot { let start_task = cx .spawn({ let http = http.clone(); diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 872182924166830416a1c779f89d477798ea147b..b3269986165d607b622b20f119ebc722ec24b03c 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -50,15 +50,16 @@ pub fn init(cx: &mut AppContext) { cx.add_action(CopilotButton::deploy_copilot_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,10 +68,9 @@ 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()) }) }); } @@ -94,7 +94,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,7 +105,9 @@ impl View for CopilotButton { }; let status = copilot.read(cx).status(); - let enabled = self.editor_enabled.unwrap_or(settings.copilot_on(None)); + let enabled = self + .editor_enabled + .unwrap_or(settings.show_copilot_suggestions(None)); let view_id = cx.view_id(); @@ -248,7 +250,7 @@ impl CopilotButton { 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!( @@ -266,7 +268,7 @@ 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" @@ -319,7 +321,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 b3f3723bc1adb7d1fa828207e9a46b4517cc3b35..2d39416858d903c7535ff99e694aee292ad3858c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2816,7 +2816,10 @@ 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()) { + if !cx + .global::() + .show_copilot_suggestions(language_name.as_deref()) + { self.hide_copilot_suggestion(cx); return None; } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index feb4017018098ffd7040e3ec2c1416b055658c8f..6942a6e57b70465fd8942df69668705087737244 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 } } } From 4a9989fe384106ec102feacb582f8ac145c9283c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Apr 2023 20:10:57 +0200 Subject: [PATCH 02/11] Clear all suggestions from `Editor` when disabling Copilot Co-Authored-By: Nathan Sobo --- crates/editor/src/editor.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2d39416858d903c7535ff99e694aee292ad3858c..45365b83810ec64afca8b448da4834fdb54b80b0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1265,7 +1265,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); @@ -2808,7 +2808,7 @@ impl Editor { fn refresh_copilot_suggestions(&mut self, 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); @@ -2820,7 +2820,7 @@ impl Editor { .global::() .show_copilot_suggestions(language_name.as_deref()) { - self.hide_copilot_suggestion(cx); + self.clear_copilot_suggestions(cx); return None; } @@ -2941,6 +2941,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, @@ -6494,7 +6499,7 @@ impl Editor { cx.notify(); } - fn on_settings_changed(&mut self, cx: &mut ViewContext) { + fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(cx); } From 54a78d702492482f9dc9789890d087cf6f1475cb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Apr 2023 20:18:06 +0200 Subject: [PATCH 03/11] Clarify Copilot context menu Co-Authored-By: Nathan Sobo --- crates/copilot_button/src/copilot_button.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index b3269986165d607b622b20f119ebc722ec24b03c..8d535cc96b0c6085b303916752f9bba1e3089605 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -254,12 +254,8 @@ impl CopilotButton { menu_options.push(ContextMenuItem::item( format!( - "{} Copilot for {}", - if language_enabled { - "Disable" - } else { - "Enable" - }, + "{} Suggestions for {}", + if language_enabled { "Hide" } else { "Show" }, language ), ToggleCopilotForLanguage { @@ -271,9 +267,9 @@ impl CopilotButton { 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, )); From 8610f3acf30f66ba73b0c6f0078f061bd4c1ced4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 Apr 2023 20:25:59 +0200 Subject: [PATCH 04/11] Introduce a button to disable copilot integration Co-Authored-By: Nathan Sobo --- crates/copilot/src/sign_in.rs | 16 +++++++++++++++- styles/src/styleTree/copilot.ts | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index a65be325ce61f6b596f9d35cb75417a724f5dd58..43b67096363c5c4ad197e195b7b9b37012f8c905 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -5,7 +5,7 @@ use gpui::{ platform::{WindowBounds, WindowKind, WindowOptions}, AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle, }; -use settings::Settings; +use settings::{settings_file::SettingsFile, Settings}; use theme::ui::modal; #[derive(PartialEq, Eq, Debug, Clone)] @@ -199,6 +199,20 @@ impl CopilotCodeVerification { }, ) .boxed(), + theme::ui::cta_button_with_click( + "Disable Copilot Integration", + style.auth.content_width, + &style.auth.cta_button, + cx, + { + move |_, cx| { + SettingsFile::update(cx, move |settings| { + settings.features.copilot = Some(false); + }); + } + }, + ) + .boxed(), ]) .align_children_center() .boxed() diff --git a/styles/src/styleTree/copilot.ts b/styles/src/styleTree/copilot.ts index 9fa86cd741234a421eade8f02161729e8ab502f0..b1dd17b3b33dd6e6f952304084c3787f5e08c575 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/styleTree/copilot.ts @@ -113,7 +113,7 @@ export default function copilot(colorScheme: ColorScheme) { }, dimensions: { width: 280, - height: 280, + height: 320, }, }, From ea1c3fa7a0fb0beebe4e954bc975652728b4bb5d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 18 Apr 2023 14:53:01 -0700 Subject: [PATCH 05/11] Only fire completion cycling requests if specifically asked for by the user --- assets/keymaps/default.json | 2 +- crates/copilot/src/copilot.rs | 5 ++- crates/editor/src/editor.rs | 72 +++++++++++++++++++++++------------ 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 14e3056e6a6e520d2014fe88315a1323761e787f..77094e53e358a1bb6b3da0e8acac3d4f908ea1e5 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -177,7 +177,7 @@ "focus": false } ], - "alt-\\": "copilot::NextSuggestion", + "alt-\\": "copilot::Suggest", "alt-]": "copilot::NextSuggestion", "alt-[": "copilot::PreviousSuggestion" } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index aa086775b3b0ef1652de8b72554fcc591faa55fb..084d7fcf7ace1f050b03d1e7605d998e21c12cc3 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. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 45365b83810ec64afca8b448da4834fdb54b80b0..dfc609fe8c7316668cddb90c61278486df3c8573 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -395,6 +395,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,7 @@ pub struct CopilotState { pending_refresh: Task>, completions: Vec, active_completion_index: usize, + cycled: bool, } impl Default for CopilotState { @@ -1025,6 +1027,7 @@ impl Default for CopilotState { pending_refresh: Task::ready(Some(())), completions: Default::default(), active_completion_index: 0, + cycled: false, } } } @@ -2026,13 +2029,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(false, 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(false, cx); } }); } @@ -2114,7 +2117,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(false, cx); }); } @@ -2512,7 +2515,7 @@ impl Editor { }); } - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(false, cx); }); let project = self.project.clone()?; @@ -2805,7 +2808,11 @@ impl Editor { None } - fn refresh_copilot_suggestions(&mut self, cx: &mut ViewContext) -> Option<()> { + fn refresh_copilot_suggestions( + &mut self, + cycling: bool, + cx: &mut ViewContext, + ) -> Option<()> { let copilot = Copilot::global(cx)?; if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { self.clear_copilot_suggestions(cx); @@ -2828,17 +2835,24 @@ impl Editor { 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), - ) - }); - 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()); + + let completions_iter = if cycling { + copilot + .update(&mut cx, |copilot, cx| { + copilot.completions(&buffer, buffer_position, cx) + }) + .await + } else { + copilot + .update(&mut cx, |copilot, cx| { + copilot.completions_cycling(&buffer, buffer_position, cx) + }) + .await + }; + completions.extend(completions_iter.log_err().into_iter().flatten()); + this.upgrade(&cx)?.update(&mut cx, |this, cx| { if !completions.is_empty() { this.copilot_state.completions.clear(); @@ -2847,6 +2861,7 @@ impl Editor { for completion in completions { this.copilot_state.push_completion(completion); } + this.copilot_state.cycled = cycling; this.update_visible_copilot_suggestion(cx); } }); @@ -2857,9 +2872,18 @@ impl Editor { Some(()) } - fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext) { + 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.update_visible_copilot_suggestion(cx); + } + + fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext) { + if !self.has_active_copilot_suggestion(cx) || !self.copilot_state.cycled { + self.refresh_copilot_suggestions(true, cx); return; } @@ -2873,8 +2897,8 @@ impl Editor { _: &copilot::PreviousSuggestion, cx: &mut ViewContext, ) { - if !self.has_active_copilot_suggestion(cx) { - self.refresh_copilot_suggestions(cx); + if !self.has_active_copilot_suggestion(cx) || !self.copilot_state.cycled { + self.refresh_copilot_suggestions(true, cx); return; } @@ -3231,7 +3255,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(false, cx); }); } @@ -3247,7 +3271,7 @@ impl Editor { }) }); this.insert("", cx); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(false, cx); }); } @@ -3343,7 +3367,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(false, cx); }); } @@ -4023,7 +4047,7 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(cx); - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(false, cx); cx.emit(Event::Edited); } } @@ -4038,7 +4062,7 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(cx); - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(false, cx); cx.emit(Event::Edited); } } @@ -6500,7 +6524,7 @@ impl Editor { } fn settings_changed(&mut self, cx: &mut ViewContext) { - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(false, cx); } pub fn set_searchable(&mut self, searchable: bool) { From 70ff4ca48f5455f7c73d984804054ba1af42d895 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Apr 2023 12:58:21 -0700 Subject: [PATCH 06/11] WIP: lower our usage of the copilot API by seperating out the cycling completion Restore copilot setting visibility co-authored-by: antonio --- crates/editor/src/editor.rs | 143 ++++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dfc609fe8c7316668cddb90c61278486df3c8573..710d1c695807e1ccd38006fa6bbe25001b1791a1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1015,15 +1015,17 @@ impl CodeActionsMenu { pub struct CopilotState { excerpt_id: Option, pending_refresh: Task>, + pending_cycling_refresh: Task>, + cycled: bool, completions: Vec, active_completion_index: usize, - cycled: bool, } 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, @@ -1071,6 +1073,22 @@ 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 => { + 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 { @@ -2029,13 +2047,13 @@ impl Editor { this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); if had_active_copilot_suggestion { - this.refresh_copilot_suggestions(false, cx); + this.refresh_copilot_suggestions(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(false, cx); + this.refresh_copilot_suggestions(cx); } }); } @@ -2117,7 +2135,7 @@ impl Editor { .collect(); this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); - this.refresh_copilot_suggestions(false, cx); + this.refresh_copilot_suggestions(cx); }); } @@ -2515,7 +2533,7 @@ impl Editor { }); } - this.refresh_copilot_suggestions(false, cx); + this.refresh_copilot_suggestions(cx); }); let project = self.project.clone()?; @@ -2808,11 +2826,7 @@ impl Editor { None } - fn refresh_copilot_suggestions( - &mut self, - cycling: bool, - cx: &mut ViewContext, - ) -> Option<()> { + fn refresh_copilot_suggestions(&mut self, cx: &mut ViewContext) -> Option<()> { let copilot = Copilot::global(cx)?; if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() { self.clear_copilot_suggestions(cx); @@ -2838,30 +2852,31 @@ impl Editor { let mut completions = Vec::new(); - let completions_iter = if cycling { - copilot - .update(&mut cx, |copilot, cx| { - copilot.completions(&buffer, buffer_position, cx) - }) - .await - } else { - copilot - .update(&mut cx, |copilot, cx| { - copilot.completions_cycling(&buffer, buffer_position, cx) - }) - .await - }; + // let completions_iter = if cycling { + let completions_iter = copilot + .update(&mut cx, |copilot, cx| { + copilot.completions(&buffer, buffer_position, cx) + }) + .await; + // } else { + // copilot + // .update(&mut cx, |copilot, cx| { + // copilot.completions_cycling(&buffer, buffer_position, cx) + // }) + // .await + // }; completions.extend(completions_iter.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); for completion in completions { this.copilot_state.push_completion(completion); } - this.copilot_state.cycled = cycling; this.update_visible_copilot_suggestion(cx); } }); @@ -2872,9 +2887,49 @@ impl Editor { Some(()) } + 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(false, cx); + self.refresh_copilot_suggestions(cx); return; } @@ -2882,14 +2937,11 @@ impl Editor { } fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext) { - if !self.has_active_copilot_suggestion(cx) || !self.copilot_state.cycled { - self.refresh_copilot_suggestions(true, cx); - return; + if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Next, cx); + } else { + self.refresh_copilot_suggestions(cx); } - - 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 previous_copilot_suggestion( @@ -2897,18 +2949,11 @@ impl Editor { _: &copilot::PreviousSuggestion, cx: &mut ViewContext, ) { - if !self.has_active_copilot_suggestion(cx) || !self.copilot_state.cycled { - self.refresh_copilot_suggestions(true, cx); - return; + if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Prev, cx); + } else { + self.refresh_copilot_suggestions(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 { @@ -3255,7 +3300,7 @@ impl Editor { this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); this.insert("", cx); - this.refresh_copilot_suggestions(false, cx); + this.refresh_copilot_suggestions(cx); }); } @@ -3271,7 +3316,7 @@ impl Editor { }) }); this.insert("", cx); - this.refresh_copilot_suggestions(false, cx); + this.refresh_copilot_suggestions(cx); }); } @@ -3367,7 +3412,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(false, cx); + this.refresh_copilot_suggestions(cx); }); } @@ -4047,7 +4092,7 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(cx); - self.refresh_copilot_suggestions(false, cx); + self.refresh_copilot_suggestions(cx); cx.emit(Event::Edited); } } @@ -4062,7 +4107,7 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(cx); - self.refresh_copilot_suggestions(false, cx); + self.refresh_copilot_suggestions(cx); cx.emit(Event::Edited); } } @@ -6524,7 +6569,7 @@ impl Editor { } fn settings_changed(&mut self, cx: &mut ViewContext) { - self.refresh_copilot_suggestions(false, cx); + self.refresh_copilot_suggestions(cx); } pub fn set_searchable(&mut self, searchable: bool) { From 745e5e3a09059b13d6e534b5e55f62b33b97122e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Apr 2023 13:54:43 -0700 Subject: [PATCH 07/11] Add italic styles to copilot suggestions --- styles/src/styleTree/editor.ts | 4 +--- styles/src/themes/common/syntax.ts | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 0c2435ed5ad3f7d9094f51ac324082c1d151ce06..84ef51406ea17e97ee3cdb91017043794130e228 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 50c3e4e3b730de7e59ac22fd583f2620f8f82ef3..925ed7e5c14ba1308fee179113879c13db25d344 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, From 2882e0fa5b7ac604d8f2f0abae27ecbecb04c674 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Apr 2023 16:39:55 -0700 Subject: [PATCH 08/11] Remove new CTA in copilot sign in UI Add a trim_end to copilot suggestions --- crates/copilot/src/sign_in.rs | 14 -------------- crates/editor/src/editor.rs | 4 ++-- styles/src/styleTree/copilot.ts | 2 +- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 43b67096363c5c4ad197e195b7b9b37012f8c905..4378c90c8812b4654ea1429a26401dcd9996fb7b 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -199,20 +199,6 @@ impl CopilotCodeVerification { }, ) .boxed(), - theme::ui::cta_button_with_click( - "Disable Copilot Integration", - style.auth.content_width, - &style.auth.cta_button, - cx, - { - move |_, cx| { - SettingsFile::update(cx, move |settings| { - settings.features.copilot = Some(false); - }); - } - }, - ) - .boxed(), ]) .align_children_center() .boxed() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 710d1c695807e1ccd38006fa6bbe25001b1791a1..cba98c73c526b34bcfd503887e41aa33976922ae 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2995,11 +2995,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, ) diff --git a/styles/src/styleTree/copilot.ts b/styles/src/styleTree/copilot.ts index b1dd17b3b33dd6e6f952304084c3787f5e08c575..9fa86cd741234a421eade8f02161729e8ab502f0 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/styleTree/copilot.ts @@ -113,7 +113,7 @@ export default function copilot(colorScheme: ColorScheme) { }, dimensions: { width: 280, - height: 320, + height: 280, }, }, From 9b8a3e4de5d75c31aa92beb4e1fb3625d72f1619 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Apr 2023 16:50:31 -0700 Subject: [PATCH 09/11] Fixed panic in new cycling code Made cycling fire without debounce --- crates/copilot/src/sign_in.rs | 2 +- crates/editor/src/editor.rs | 47 ++++++++++++++++------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 4378c90c8812b4654ea1429a26401dcd9996fb7b..a65be325ce61f6b596f9d35cb75417a724f5dd58 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -5,7 +5,7 @@ use gpui::{ platform::{WindowBounds, WindowKind, WindowOptions}, AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle, }; -use settings::{settings_file::SettingsFile, Settings}; +use settings::Settings; use theme::ui::modal; #[derive(PartialEq, Eq, Debug, Clone)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cba98c73c526b34bcfd503887e41aa33976922ae..de47fbd4edf4d545266b5575cc001990751da158 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1083,8 +1083,12 @@ impl CopilotState { }; } Direction::Next => { - self.active_completion_index = - (self.active_completion_index + 1) % self.completions.len(); + if self.completions.len() == 0 { + self.active_completion_index = 0 + } else { + self.active_completion_index = + (self.active_completion_index + 1) % self.completions.len(); + } } } } @@ -2850,22 +2854,15 @@ impl Editor { self.copilot_state.pending_refresh = cx.spawn_weak(|this, mut cx| async move { cx.background().timer(COPILOT_DEBOUNCE_TIMEOUT).await; - let mut completions = Vec::new(); - - // let completions_iter = if cycling { - let completions_iter = copilot + let completions = copilot .update(&mut cx, |copilot, cx| { copilot.completions(&buffer, buffer_position, cx) }) - .await; - // } else { - // copilot - // .update(&mut cx, |copilot, cx| { - // copilot.completions_cycling(&buffer, buffer_position, cx) - // }) - // .await - // }; - completions.extend(completions_iter.log_err().into_iter().flatten()); + .await + .log_err() + .into_iter() + .flatten() + .collect_vec(); this.upgrade(&cx)?.update(&mut cx, |this, cx| { if !completions.is_empty() { @@ -2937,11 +2934,11 @@ impl Editor { } 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(cx); - } + // if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Next, cx); + // } else { + // self.refresh_copilot_suggestions(cx); + // } } fn previous_copilot_suggestion( @@ -2949,11 +2946,11 @@ impl Editor { _: &copilot::PreviousSuggestion, cx: &mut ViewContext, ) { - if self.has_active_copilot_suggestion(cx) { - self.cycle_suggestions(Direction::Prev, cx); - } else { - self.refresh_copilot_suggestions(cx); - } + // if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Prev, cx); + // } else { + // self.refresh_copilot_suggestions(cx); + // } } fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { From f16b96cafcd24e98f5e86bb9baa3b495342e0487 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Apr 2023 17:27:44 -0700 Subject: [PATCH 10/11] add copilot menu --- crates/copilot/src/copilot.rs | 4 +- crates/copilot/src/sign_in.rs | 47 +++++++- crates/copilot_button/src/copilot_button.rs | 124 ++++++++++++++------ 3 files changed, 130 insertions(+), 45 deletions(-) diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 084d7fcf7ace1f050b03d1e7605d998e21c12cc3..1967c3cd14d0045110811680d5cce39d191a8d60 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -202,7 +202,9 @@ impl Copilot { .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 a65be325ce61f6b596f9d35cb75417a724f5dd58..dc09ddf3f247a35242f9c85985c2f297524883e7 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 8d535cc96b0c6085b303916752f9bba1e3089605..b7e93536b9833a2ec72438b3e94ae91b33bab478 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,6 +60,7 @@ 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.clone(); @@ -73,6 +86,58 @@ pub fn init(cx: &mut AppContext) { 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 { @@ -109,8 +174,6 @@ impl View for CopilotButton { .editor_enabled .unwrap_or(settings.show_copilot_suggestions(None)); - let view_id = cx.view_id(); - Stack::new() .with_child( MouseEventHandler::::new(0, cx, { @@ -157,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::( @@ -244,6 +272,26 @@ 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::(); From 26ab774b7faac4cf7129a65bfdabb73c7207e68c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 19 Apr 2023 17:34:09 -0700 Subject: [PATCH 11/11] Removed debounce on suggestion cycling code --- crates/editor/src/editor.rs | 52 +++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index de47fbd4edf4d545266b5575cc001990751da158..308f0079d0589215c9cd94d43b5302c3938f91a6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2051,13 +2051,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); } }); } @@ -2139,7 +2139,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); }); } @@ -2537,7 +2537,7 @@ impl Editor { }); } - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); let project = self.project.clone()?; @@ -2830,7 +2830,11 @@ 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.clear_copilot_suggestions(cx); @@ -2852,7 +2856,9 @@ impl Editor { 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; + if debounce { + cx.background().timer(COPILOT_DEBOUNCE_TIMEOUT).await; + } let completions = copilot .update(&mut cx, |copilot, cx| { @@ -2926,7 +2932,7 @@ impl Editor { 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; } @@ -2934,11 +2940,11 @@ impl Editor { } 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(cx); - // } + if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Next, cx); + } else { + self.refresh_copilot_suggestions(false, cx); + } } fn previous_copilot_suggestion( @@ -2946,11 +2952,11 @@ impl Editor { _: &copilot::PreviousSuggestion, cx: &mut ViewContext, ) { - // if self.has_active_copilot_suggestion(cx) { - self.cycle_suggestions(Direction::Prev, cx); - // } else { - // self.refresh_copilot_suggestions(cx); - // } + if self.has_active_copilot_suggestion(cx) { + self.cycle_suggestions(Direction::Prev, cx); + } else { + self.refresh_copilot_suggestions(false, cx); + } } fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { @@ -3297,7 +3303,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); }); } @@ -3313,7 +3319,7 @@ impl Editor { }) }); this.insert("", cx); - this.refresh_copilot_suggestions(cx); + this.refresh_copilot_suggestions(true, cx); }); } @@ -3409,7 +3415,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); }); } @@ -4089,7 +4095,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); } } @@ -4104,7 +4110,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); } } @@ -6566,7 +6572,7 @@ impl Editor { } fn settings_changed(&mut self, cx: &mut ViewContext) { - self.refresh_copilot_suggestions(cx); + self.refresh_copilot_suggestions(true, cx); } pub fn set_searchable(&mut self, searchable: bool) {