mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-27 23:59:52 +03:00
Merge pull request #2386 from zed-industries/copilot-shipping
Get copilot ready to ship
This commit is contained in:
commit
dd73233973
@ -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.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::<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,
|
||||
|
Loading…
Reference in New Issue
Block a user