Skip to content

copilot: Use latest Copilot API spec #30249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 126 additions & 43 deletions crates/copilot/src/copilot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use language::{
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
use node_runtime::NodeRuntime;
use parking_lot::Mutex;
use request::StatusNotification;
use request::DidChangeStatus;
use settings::SettingsStore;
use sign_in::{reinstall_and_sign_in_within_workspace, sign_out_within_workspace};
use std::{
Expand Down Expand Up @@ -471,6 +471,7 @@ impl Copilot {
awaiting_sign_in_after_start: bool,
cx: &mut AsyncApp,
) {
let _ = cx;
let start_language_server = async {
let server_path = get_copilot_lsp(fs, node_runtime.clone()).await?;
let node_path = node_runtime.binary_path().await?;
Expand Down Expand Up @@ -500,14 +501,93 @@ impl Copilot {
)?;

server
.on_notification::<StatusNotification, _>(|_, _| { /* Silence the notification */ })
.on_notification::<DidChangeStatus, _>({
let this = this.clone();
move |params, cx| {
// Convert didChangeStatus to appropriate SignInStatus
let sign_in_status = match params.kind.as_str() {
"Error" => Some(request::SignInStatus::NotAuthorized {
user: String::new(),
}),
"Normal" => Some(request::SignInStatus::AlreadySignedIn {
user: String::new(),
}),
"Inactive" | "Warning" => None, // Don't change auth status for these
_ => None,
};

if let Some(status) = sign_in_status {
this.update(cx, |this, cx| {
// Check current status before deciding to update
if let CopilotServer::Running(server) = &this.server {
let should_update = match (&server.sign_in_status, &status) {
// If currently signing in, only update if receiving Authorized status
(
SignInStatus::SigningIn { .. },
request::SignInStatus::AlreadySignedIn { .. },
)
| (
SignInStatus::SigningIn { .. },
request::SignInStatus::Ok { user: Some(_) },
)
| (
SignInStatus::SigningIn { .. },
request::SignInStatus::MaybeOk { .. },
) => true,

// Don't interrupt sign-in flow with an error
(SignInStatus::SigningIn { .. }, _) => false,

// Avoid redundant transitions between signed-out states
(
SignInStatus::SignedOut { .. },
request::SignInStatus::NotSignedIn,
)
| (
SignInStatus::SignedOut { .. },
request::SignInStatus::Ok { user: None },
) => false,

// Avoid redundant transitions between authorized states
(
SignInStatus::Authorized,
request::SignInStatus::AlreadySignedIn { .. },
)
| (
SignInStatus::Authorized,
request::SignInStatus::MaybeOk { .. },
)
| (
SignInStatus::Authorized,
request::SignInStatus::Ok { user: Some(_) },
) => false,

// Avoid redundant transitions between unauthorized states
(
SignInStatus::Unauthorized,
request::SignInStatus::NotAuthorized { .. },
) => false,

// Allow all other transitions
_ => true,
};

if should_update {
this.update_sign_in_status(status, cx);
}
}
})
.ok();
}
}
})
.detach();

let configuration = lsp::DidChangeConfigurationParams {
settings: Default::default(),
};

let editor_info = request::SetEditorInfoParams {
let initialization_options = request::InitializationOptions {
editor_info: request::EditorInfo {
name: "zed".into(),
version: env!("CARGO_PKG_VERSION").into(),
Expand All @@ -517,12 +597,12 @@ impl Copilot {
version: "0.0.1".into(),
},
};
let editor_info_json = serde_json::to_value(&editor_info)?;
let init_options_json = serde_json::to_value(&initialization_options)?;

let server = cx
.update(|cx| {
let mut params = server.default_initialize_params(cx);
params.initialization_options = Some(editor_info_json);
params.initialization_options = Some(init_options_json);
server.initialize(params, configuration.into(), cx)
})?
.await?;
Expand All @@ -533,10 +613,6 @@ impl Copilot {
})
.await?;

server
.request::<request::SetEditorInfo>(editor_info)
.await?;

anyhow::Ok((server, status))
};

Expand Down Expand Up @@ -578,15 +654,13 @@ impl Copilot {
.spawn(async move |this, cx| {
let sign_in = async {
let sign_in = lsp
.request::<request::SignInInitiate>(
request::SignInInitiateParams {},
)
.request::<request::SignIn>(request::SignInParams {})
.await?;
match sign_in {
request::SignInInitiateResult::AlreadySignedIn { user } => {
Ok(request::SignInStatus::Ok { user: Some(user) })
request::SignInResult::AlreadySignedIn {} => {
Ok(request::SignInStatus::Ok { user: None })
}
request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
request::SignInResult::PromptUserDeviceFlow(flow) => {
this.update(cx, |this, cx| {
if let CopilotServer::Running(RunningCopilotServer {
sign_in_status: status,
Expand Down Expand Up @@ -842,7 +916,7 @@ impl Copilot {
where
T: ToPointUtf16,
{
self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
self.request_completions::<request::TextDocumentInlineCompletion, _>(buffer, position, cx)
}

pub fn completions_cycling<T>(
Expand All @@ -854,7 +928,7 @@ impl Copilot {
where
T: ToPointUtf16,
{
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
self.request_completions::<request::TextDocumentInlineCompletion, _>(buffer, position, cx)
}

pub fn accept_completion(
Expand All @@ -866,12 +940,15 @@ impl Copilot {
Ok(server) => server,
Err(error) => return Task::ready(Err(error)),
};
let request =
server
.lsp
.request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
uuid: completion.uuid.clone(),
});
let request = server
.lsp
.request::<lsp::ExecuteCommand>(lsp::ExecuteCommandParams {
command: "github.copilot.didAcceptCompletionItem".into(),
arguments: vec![serde_json::Value::String(completion.uuid.clone())],
work_done_progress_params: lsp::WorkDoneProgressParams {
work_done_token: None,
},
});
cx.background_spawn(async move {
request.await?;
Ok(())
Expand Down Expand Up @@ -911,8 +988,8 @@ impl Copilot {
where
R: 'static
+ lsp::request::Request<
Params = request::GetCompletionsParams,
Result = request::GetCompletionsResult,
Params = request::TextDocumentInlineCompletionParams,
Result = request::TextDocumentInlineCompletionResult,
>,
T: ToPointUtf16,
{
Expand All @@ -938,38 +1015,46 @@ impl Copilot {
);
let tab_size = settings.tab_size;
let hard_tabs = settings.hard_tabs;
let relative_path = buffer
.file()
.map(|file| file.path().to_path_buf())
.unwrap_or_default();
// let relative_path = buffer
// .file()
// .map(|file| file.path().to_path_buf())
// .unwrap_or_default();

cx.background_spawn(async move {
let (version, snapshot) = snapshot.await?;
let result = lsp
.request::<R>(request::GetCompletionsParams {
doc: request::GetCompletionsDocument {
uri,
.request::<R>(request::TextDocumentInlineCompletionParams {
text_document: request::TextDocumentIdentifier {
uri: uri.clone(),
version: version.try_into().unwrap(),
},
formatting_options: request::FormattingOptions {
tab_size: tab_size.into(),
indent_size: 1,
insert_spaces: !hard_tabs,
relative_path: relative_path.to_string_lossy().into(),
position: point_to_lsp(position),
version: version.try_into().unwrap(),
},
position: point_to_lsp(position),
context: request::InlineCompletionContext { trigger_kind: 2 },
})
.await?;
let completions = result
.completions
.items
.into_iter()
.map(|completion| {
let start = snapshot
.clip_point_utf16(point_from_lsp(completion.range.start), Bias::Left);
let end =
snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
Completion {
uuid: completion.uuid,
uuid: completion
.command
.arguments
.as_ref()
.and_then(|args| args.get(0))
.and_then(|val| val.as_str())
.map(String::from)
.unwrap_or_default(),
range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
text: completion.text,
text: completion.insert_text,
}
})
.collect();
Expand Down Expand Up @@ -1208,10 +1293,8 @@ mod tests {
);

// Ensure all previously-registered buffers are re-opened when signing in.
lsp.set_request_handler::<request::SignInInitiate, _, _>(|_, _| async {
Ok(request::SignInInitiateResult::AlreadySignedIn {
user: "user-1".into(),
})
lsp.set_request_handler::<request::SignIn, _, _>(|_, _| async {
Ok(request::SignInResult::AlreadySignedIn {})
});
copilot
.update(cx, |copilot, cx| copilot.sign_in(cx))
Expand Down
62 changes: 45 additions & 17 deletions crates/copilot/src/copilot_completion_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,16 +1003,20 @@ mod tests {
.unwrap();

let mut copilot_requests = copilot_lsp
.set_request_handler::<crate::request::GetCompletions, _, _>(
.set_request_handler::<crate::request::TextDocumentInlineCompletion, _, _>(
move |_params, _cx| async move {
Ok(crate::request::GetCompletionsResult {
completions: vec![crate::request::Completion {
text: "next line".into(),
Ok(crate::request::TextDocumentInlineCompletionResult {
items: vec![crate::request::InlineCompletionItem {
insert_text: "next line".into(),
range: lsp::Range::new(
lsp::Position::new(1, 0),
lsp::Position::new(1, 0),
),
..Default::default()
command: lsp::Command {
command: "github.copilot.didAcceptCompletionItem".into(),
arguments: Some(vec!["uuid".into()]),
title: "Copilot".to_string(),
},
}],
})
},
Expand Down Expand Up @@ -1044,20 +1048,44 @@ mod tests {
completions: Vec<crate::request::Completion>,
completions_cycling: Vec<crate::request::Completion>,
) {
lsp.set_request_handler::<crate::request::GetCompletions, _, _>(move |_params, _cx| {
let completions = completions.clone();
async move {
Ok(crate::request::GetCompletionsResult {
completions: completions.clone(),
})
}
});
lsp.set_request_handler::<crate::request::GetCompletionsCycling, _, _>(
lsp.set_request_handler::<crate::request::TextDocumentInlineCompletion, _, _>(
move |_params, _cx| {
let completions = completions
.clone()
.into_iter()
.map(|completion| crate::request::InlineCompletionItem {
insert_text: completion.text.clone(),
range: completion.range,
command: lsp::Command {
command: "github.copilot.didAcceptCompletionItem".into(),
arguments: Some(vec![json!(completion.uuid.to_string())]),
title: "Copilot".to_string(),
},
})
.collect::<Vec<_>>();
async move {
Ok(crate::request::TextDocumentInlineCompletionResult { items: completions })
}
},
);
lsp.set_request_handler::<crate::request::TextDocumentInlineCompletion, _, _>(
move |_params, _cx| {
let completions_cycling = completions_cycling.clone();
let completions_cycling = completions_cycling
.clone()
.into_iter()
.map(|completion| crate::request::InlineCompletionItem {
insert_text: completion.text.clone(),
range: completion.range,
command: lsp::Command {
command: "github.copilot.didAcceptCompletionItem".into(),
arguments: Some(vec![json!(completion.uuid.to_string())]),
title: "Copilot".to_string(),
},
})
.collect::<Vec<_>>();
async move {
Ok(crate::request::GetCompletionsResult {
completions: completions_cycling.clone(),
Ok(crate::request::TextDocumentInlineCompletionResult {
items: completions_cycling,
})
}
},
Expand Down
Loading
Loading