Detailed changes
@@ -178,7 +178,7 @@
"focus": false
}
],
- "alt-\\": "copilot::NextSuggestion",
+ "alt-\\": "copilot::Suggest",
"alt-]": "copilot::NextSuggestion",
"alt-[": "copilot::PreviousSuggestion"
}
@@ -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.
@@ -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<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, 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::<Settings>().enable_copilot_integration {
+ if cx.global::<Settings>().features.copilot {
if matches!(this.server, CopilotServer::Disabled) {
let start_task = cx
.spawn({
@@ -194,12 +197,14 @@ impl Copilot {
})
.detach();
- if cx.global::<Settings>().enable_copilot_integration {
+ if cx.global::<Settings>().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();
@@ -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<Self>) {
@@ -143,6 +159,7 @@ impl CopilotCodeVerification {
}
fn render_prompting_modal(
+ connect_clicked: bool,
data: &PromptUserDeviceFlow,
style: &theme::Copilot,
cx: &mut gpui::RenderContext<Self>,
@@ -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(),
},
])
@@ -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<str>,
@@ -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::<Settings>().copilot_on(Some(&language));
+ let language = action.language.clone();
+ let show_copilot_suggestions = cx
+ .global::<Settings>()
+ .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::<Settings>().copilot_on(None);
+ let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None);
+ SettingsFile::update(cx, move |file_contents| {
+ 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.editor.copilot = Some((!copilot_on).into())
+ 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::<Settings>();
- 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::<Self, _>(
@@ -242,22 +272,38 @@ impl CopilotButton {
}
}
+ pub fn deploy_copilot_start_menu(
+ &mut self,
+ _: &DeployCopilotStartMenu,
+ cx: &mut ViewContext<Self>,
+ ) {
+ 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<Self>) {
let settings = cx.global::<Settings>();
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::<Settings>().copilot_on(None);
+ let globally_enabled = cx.global::<Settings>().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()
}
@@ -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<ExcerptId>,
pending_refresh: Task<Option<()>>,
+ pending_cycling_refresh: Task<Option<()>>,
+ cycled: bool,
completions: Vec<copilot::Completion>,
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::<Settings, _>(Self::on_settings_changed),
+ cx.observe_global::<Settings, _>(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<Self>) -> Option<()> {
+ fn refresh_copilot_suggestions(
+ &mut self,
+ debounce: bool,
+ cx: &mut ViewContext<Self>,
+ ) -> 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::<Settings>().copilot_on(language_name.as_deref()) {
- self.hide_copilot_suggestion(cx);
+ if !cx
+ .global::<Settings>()
+ .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<Self>) {
+ fn cycle_suggestions(
+ &mut self,
+ direction: Direction,
+ cx: &mut ViewContext<Self>,
+ ) -> 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<Self>) {
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<Self>) {
+ 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<Self>,
) {
- 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<Self>) -> 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>) {
+ 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>) {
- self.refresh_copilot_suggestions(cx);
+ fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
+ self.refresh_copilot_suggestions(true, cx);
}
pub fn set_searchable(&mut self, searchable: bool) {
@@ -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<bool>,
pub formatter: Option<Formatter>,
pub enable_language_server: Option<bool>,
- #[schemars(skip)]
- pub copilot: Option<OnOff>,
-}
-
-#[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<OnOff> for bool {
- fn from(value: OnOff) -> bool {
- value.as_bool()
- }
-}
-
-impl From<bool> for OnOff {
- fn from(value: bool) -> OnOff {
- OnOff::from_bool(value)
- }
+ pub show_copilot_suggestions: Option<bool>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -437,8 +401,7 @@ pub struct SettingsFileContent {
#[serde(default)]
pub base_keymap: Option<BaseKeymap>,
#[serde(default)]
- #[schemars(skip)]
- pub enable_copilot_integration: Option<bool>,
+ pub features: FeaturesContent,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -447,6 +410,18 @@ pub struct LspSettings {
pub initialization_options: Option<Value>,
}
+#[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<bool>,
+}
+
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
}
}
}
@@ -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"),
@@ -181,6 +181,7 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax {
},
predictive: {
color: color.predictive,
+ italic: true,
},
emphasis: {
color: color.emphasis,