copilot: Switch to official @github/copilot-language-server
(#27401)
This PR updates Copilot to use the official [`@github/copilot-language-server`](https://github.com/github/copilot-language-server-release). It's [available on npm](https://www.npmjs.com/package/@github/copilot-language-server), so we're installing it from there. I tested it out locally and it seemed to be working as expected. Release Notes: - Updated Copilot to use the official [`@github/copilot-language-server`](https://github.com/github/copilot-language-server-release).
This commit is contained in:
parent
be83c5e1c5
commit
a52095f558
3 changed files with 42 additions and 99 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3243,9 +3243,7 @@ name = "copilot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-tar",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
|
@ -3273,7 +3271,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"smol",
|
|
||||||
"strum",
|
"strum",
|
||||||
"task",
|
"task",
|
||||||
"theme",
|
"theme",
|
||||||
|
|
|
@ -26,8 +26,6 @@ test-support = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-compression.workspace = true
|
|
||||||
async-tar.workspace = true
|
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
@ -49,7 +47,6 @@ schemars = { workspace = true, optional = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smol.workspace = true
|
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
task.workspace = true
|
task.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
|
|
@ -4,9 +4,7 @@ pub mod request;
|
||||||
mod sign_in;
|
mod sign_in;
|
||||||
|
|
||||||
use ::fs::Fs;
|
use ::fs::Fs;
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
|
||||||
use async_tar::Archive;
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use command_palette_hooks::CommandPaletteFilter;
|
use command_palette_hooks::CommandPaletteFilter;
|
||||||
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||||
|
@ -14,7 +12,6 @@ use gpui::{
|
||||||
actions, App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task,
|
actions, App, AppContext as _, AsyncApp, Context, Entity, EntityId, EventEmitter, Global, Task,
|
||||||
WeakEntity,
|
WeakEntity,
|
||||||
};
|
};
|
||||||
use http_client::github::get_release_by_tag_name;
|
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
use language::language_settings::CopilotSettings;
|
use language::language_settings::CopilotSettings;
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -27,7 +24,6 @@ use node_runtime::NodeRuntime;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use request::StatusNotification;
|
use request::StatusNotification;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
env,
|
env,
|
||||||
|
@ -37,7 +33,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::{fs::remove_matching, maybe, ResultExt};
|
use util::{fs::remove_matching, ResultExt};
|
||||||
|
|
||||||
pub use crate::copilot_completion_provider::CopilotCompletionProvider;
|
pub use crate::copilot_completion_provider::CopilotCompletionProvider;
|
||||||
pub use crate::sign_in::{initiate_sign_in, CopilotCodeVerification};
|
pub use crate::sign_in::{initiate_sign_in, CopilotCodeVerification};
|
||||||
|
@ -65,7 +61,7 @@ pub fn init(
|
||||||
|
|
||||||
let copilot = cx.new({
|
let copilot = cx.new({
|
||||||
let node_runtime = node_runtime.clone();
|
let node_runtime = node_runtime.clone();
|
||||||
move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
|
move |cx| Copilot::start(new_server_id, node_runtime, cx)
|
||||||
});
|
});
|
||||||
Copilot::set_global(copilot.clone(), cx);
|
Copilot::set_global(copilot.clone(), cx);
|
||||||
cx.observe(&copilot, |handle, cx| {
|
cx.observe(&copilot, |handle, cx| {
|
||||||
|
@ -305,7 +301,6 @@ pub struct Completion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Copilot {
|
pub struct Copilot {
|
||||||
http: Arc<dyn HttpClient>,
|
|
||||||
node_runtime: NodeRuntime,
|
node_runtime: NodeRuntime,
|
||||||
server: CopilotServer,
|
server: CopilotServer,
|
||||||
buffers: HashSet<WeakEntity<Buffer>>,
|
buffers: HashSet<WeakEntity<Buffer>>,
|
||||||
|
@ -337,13 +332,11 @@ impl Copilot {
|
||||||
|
|
||||||
fn start(
|
fn start(
|
||||||
new_server_id: LanguageServerId,
|
new_server_id: LanguageServerId,
|
||||||
http: Arc<dyn HttpClient>,
|
|
||||||
node_runtime: NodeRuntime,
|
node_runtime: NodeRuntime,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
server_id: new_server_id,
|
server_id: new_server_id,
|
||||||
http,
|
|
||||||
node_runtime,
|
node_runtime,
|
||||||
server: CopilotServer::Disabled,
|
server: CopilotServer::Disabled,
|
||||||
buffers: Default::default(),
|
buffers: Default::default(),
|
||||||
|
@ -384,14 +377,12 @@ impl Copilot {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let server_id = self.server_id;
|
let server_id = self.server_id;
|
||||||
let http = self.http.clone();
|
|
||||||
let node_runtime = self.node_runtime.clone();
|
let node_runtime = self.node_runtime.clone();
|
||||||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||||
let start_task = cx
|
let start_task = cx
|
||||||
.spawn(async move |this, cx| {
|
.spawn(async move |this, cx| {
|
||||||
Self::start_language_server(
|
Self::start_language_server(
|
||||||
server_id,
|
server_id,
|
||||||
http,
|
|
||||||
node_runtime,
|
node_runtime,
|
||||||
env,
|
env,
|
||||||
this,
|
this,
|
||||||
|
@ -445,11 +436,9 @@ impl Copilot {
|
||||||
Default::default(),
|
Default::default(),
|
||||||
&mut cx.to_async(),
|
&mut cx.to_async(),
|
||||||
);
|
);
|
||||||
let http = http_client::FakeHttpClient::create(|_| async { unreachable!() });
|
|
||||||
let node_runtime = NodeRuntime::unavailable();
|
let node_runtime = NodeRuntime::unavailable();
|
||||||
let this = cx.new(|cx| Self {
|
let this = cx.new(|cx| Self {
|
||||||
server_id: LanguageServerId(0),
|
server_id: LanguageServerId(0),
|
||||||
http: http.clone(),
|
|
||||||
node_runtime,
|
node_runtime,
|
||||||
server: CopilotServer::Running(RunningCopilotServer {
|
server: CopilotServer::Running(RunningCopilotServer {
|
||||||
lsp: Arc::new(server),
|
lsp: Arc::new(server),
|
||||||
|
@ -464,7 +453,6 @@ impl Copilot {
|
||||||
|
|
||||||
async fn start_language_server(
|
async fn start_language_server(
|
||||||
new_server_id: LanguageServerId,
|
new_server_id: LanguageServerId,
|
||||||
http: Arc<dyn HttpClient>,
|
|
||||||
node_runtime: NodeRuntime,
|
node_runtime: NodeRuntime,
|
||||||
env: Option<HashMap<String, String>>,
|
env: Option<HashMap<String, String>>,
|
||||||
this: WeakEntity<Self>,
|
this: WeakEntity<Self>,
|
||||||
|
@ -472,7 +460,7 @@ impl Copilot {
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) {
|
) {
|
||||||
let start_language_server = async {
|
let start_language_server = async {
|
||||||
let server_path = get_copilot_lsp(http).await?;
|
let server_path = get_copilot_lsp(node_runtime.clone()).await?;
|
||||||
let node_path = node_runtime.binary_path().await?;
|
let node_path = node_runtime.binary_path().await?;
|
||||||
let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
let arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
||||||
let binary = LanguageServerBinary {
|
let binary = LanguageServerBinary {
|
||||||
|
@ -506,9 +494,23 @@ impl Copilot {
|
||||||
let configuration = lsp::DidChangeConfigurationParams {
|
let configuration = lsp::DidChangeConfigurationParams {
|
||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let editor_info = request::SetEditorInfoParams {
|
||||||
|
editor_info: request::EditorInfo {
|
||||||
|
name: "zed".into(),
|
||||||
|
version: env!("CARGO_PKG_VERSION").into(),
|
||||||
|
},
|
||||||
|
editor_plugin_info: request::EditorPluginInfo {
|
||||||
|
name: "zed-copilot".into(),
|
||||||
|
version: "0.0.1".into(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let editor_info_json = serde_json::to_value(&editor_info)?;
|
||||||
|
|
||||||
let server = cx
|
let server = cx
|
||||||
.update(|cx| {
|
.update(|cx| {
|
||||||
let params = server.default_initialize_params(cx);
|
let mut params = server.default_initialize_params(cx);
|
||||||
|
params.initialization_options = Some(editor_info_json);
|
||||||
server.initialize(params, configuration.into(), cx)
|
server.initialize(params, configuration.into(), cx)
|
||||||
})?
|
})?
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -520,16 +522,7 @@ impl Copilot {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
server
|
server
|
||||||
.request::<request::SetEditorInfo>(request::SetEditorInfoParams {
|
.request::<request::SetEditorInfo>(editor_info)
|
||||||
editor_info: request::EditorInfo {
|
|
||||||
name: "zed".into(),
|
|
||||||
version: env!("CARGO_PKG_VERSION").into(),
|
|
||||||
},
|
|
||||||
editor_plugin_info: request::EditorPluginInfo {
|
|
||||||
name: "zed-copilot".into(),
|
|
||||||
version: "0.0.1".into(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
anyhow::Ok((server, status))
|
anyhow::Ok((server, status))
|
||||||
|
@ -668,13 +661,11 @@ impl Copilot {
|
||||||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||||
let start_task = cx
|
let start_task = cx
|
||||||
.spawn({
|
.spawn({
|
||||||
let http = self.http.clone();
|
|
||||||
let node_runtime = self.node_runtime.clone();
|
let node_runtime = self.node_runtime.clone();
|
||||||
let server_id = self.server_id;
|
let server_id = self.server_id;
|
||||||
async move |this, cx| {
|
async move |this, cx| {
|
||||||
clear_copilot_dir().await;
|
clear_copilot_dir().await;
|
||||||
Self::start_language_server(server_id, http, node_runtime, env, this, false, cx)
|
Self::start_language_server(server_id, node_runtime, env, this, false, cx).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.shared();
|
.shared();
|
||||||
|
@ -1056,73 +1047,31 @@ async fn clear_copilot_config_dir() {
|
||||||
remove_matching(copilot_chat::copilot_chat_config_dir(), |_| true).await
|
remove_matching(copilot_chat::copilot_chat_config_dir(), |_| true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
async fn get_copilot_lsp(node_runtime: NodeRuntime) -> anyhow::Result<PathBuf> {
|
||||||
const SERVER_PATH: &str = "dist/language-server.js";
|
const PACKAGE_NAME: &str = "@github/copilot-language-server";
|
||||||
|
const SERVER_PATH: &str =
|
||||||
|
"node_modules/@github/copilot-language-server/dist/language-server.js";
|
||||||
|
|
||||||
///Check for the latest copilot language server and download it if we haven't already
|
let latest_version = node_runtime
|
||||||
async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
.npm_package_latest_version(PACKAGE_NAME)
|
||||||
let release =
|
.await?;
|
||||||
get_release_by_tag_name("zed-industries/copilot", "v0.7.0", http.clone()).await?;
|
let server_path = paths::copilot_dir().join(SERVER_PATH);
|
||||||
|
|
||||||
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
|
let should_install = node_runtime
|
||||||
|
.should_install_npm_package(
|
||||||
fs::create_dir_all(version_dir).await?;
|
PACKAGE_NAME,
|
||||||
let server_path = version_dir.join(SERVER_PATH);
|
&server_path,
|
||||||
|
paths::copilot_dir(),
|
||||||
if fs::metadata(&server_path).await.is_err() {
|
&latest_version,
|
||||||
// Copilot LSP looks for this dist dir specifically, so lets add it in.
|
)
|
||||||
let dist_dir = version_dir.join("dist");
|
.await;
|
||||||
fs::create_dir_all(dist_dir.as_path()).await?;
|
if should_install {
|
||||||
|
node_runtime
|
||||||
let url = &release
|
.npm_install_packages(paths::copilot_dir(), &[(PACKAGE_NAME, &latest_version)])
|
||||||
.assets
|
.await?;
|
||||||
.first()
|
|
||||||
.context("Github release for copilot contained no assets")?
|
|
||||||
.browser_download_url;
|
|
||||||
|
|
||||||
let mut response = http
|
|
||||||
.get(url, Default::default(), true)
|
|
||||||
.await
|
|
||||||
.context("error downloading copilot release")?;
|
|
||||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
|
||||||
let archive = Archive::new(decompressed_bytes);
|
|
||||||
archive.unpack(dist_dir).await?;
|
|
||||||
|
|
||||||
remove_matching(paths::copilot_dir(), |entry| entry != version_dir).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(server_path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match fetch_latest(http).await {
|
Ok(server_path)
|
||||||
ok @ Result::Ok(..) => ok,
|
|
||||||
e @ Err(..) => {
|
|
||||||
e.log_err();
|
|
||||||
// Fetch a cached binary, if it exists
|
|
||||||
maybe!(async {
|
|
||||||
let mut last_version_dir = None;
|
|
||||||
let mut entries = fs::read_dir(paths::copilot_dir()).await?;
|
|
||||||
while let Some(entry) = entries.next().await {
|
|
||||||
let entry = entry?;
|
|
||||||
if entry.file_type().await?.is_dir() {
|
|
||||||
last_version_dir = Some(entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let last_version_dir =
|
|
||||||
last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
|
||||||
let server_path = last_version_dir.join(SERVER_PATH);
|
|
||||||
if server_path.exists() {
|
|
||||||
Ok(server_path)
|
|
||||||
} else {
|
|
||||||
Err(anyhow!(
|
|
||||||
"missing executable in directory {:?}",
|
|
||||||
last_version_dir
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue