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"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-compression",
|
||||
"async-std",
|
||||
"async-tar",
|
||||
"chrono",
|
||||
"client",
|
||||
"clock",
|
||||
|
@ -3273,7 +3271,6 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"strum",
|
||||
"task",
|
||||
"theme",
|
||||
|
|
|
@ -26,8 +26,6 @@ test-support = [
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
chrono.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
|
@ -49,7 +47,6 @@ schemars = { workspace = true, optional = true }
|
|||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
strum.workspace = true
|
||||
task.workspace = true
|
||||
ui.workspace = true
|
||||
|
|
|
@ -4,9 +4,7 @@ pub mod request;
|
|||
mod sign_in;
|
||||
|
||||
use ::fs::Fs;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{HashMap, HashSet};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
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,
|
||||
WeakEntity,
|
||||
};
|
||||
use http_client::github::get_release_by_tag_name;
|
||||
use http_client::HttpClient;
|
||||
use language::language_settings::CopilotSettings;
|
||||
use language::{
|
||||
|
@ -27,7 +24,6 @@ use node_runtime::NodeRuntime;
|
|||
use parking_lot::Mutex;
|
||||
use request::StatusNotification;
|
||||
use settings::SettingsStore;
|
||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
env,
|
||||
|
@ -37,7 +33,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
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::sign_in::{initiate_sign_in, CopilotCodeVerification};
|
||||
|
@ -65,7 +61,7 @@ pub fn init(
|
|||
|
||||
let copilot = cx.new({
|
||||
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);
|
||||
cx.observe(&copilot, |handle, cx| {
|
||||
|
@ -305,7 +301,6 @@ pub struct Completion {
|
|||
}
|
||||
|
||||
pub struct Copilot {
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
server: CopilotServer,
|
||||
buffers: HashSet<WeakEntity<Buffer>>,
|
||||
|
@ -337,13 +332,11 @@ impl Copilot {
|
|||
|
||||
fn start(
|
||||
new_server_id: LanguageServerId,
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mut this = Self {
|
||||
server_id: new_server_id,
|
||||
http,
|
||||
node_runtime,
|
||||
server: CopilotServer::Disabled,
|
||||
buffers: Default::default(),
|
||||
|
@ -384,14 +377,12 @@ impl Copilot {
|
|||
return;
|
||||
}
|
||||
let server_id = self.server_id;
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||
let start_task = cx
|
||||
.spawn(async move |this, cx| {
|
||||
Self::start_language_server(
|
||||
server_id,
|
||||
http,
|
||||
node_runtime,
|
||||
env,
|
||||
this,
|
||||
|
@ -445,11 +436,9 @@ impl Copilot {
|
|||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
);
|
||||
let http = http_client::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
let this = cx.new(|cx| Self {
|
||||
server_id: LanguageServerId(0),
|
||||
http: http.clone(),
|
||||
node_runtime,
|
||||
server: CopilotServer::Running(RunningCopilotServer {
|
||||
lsp: Arc::new(server),
|
||||
|
@ -464,7 +453,6 @@ impl Copilot {
|
|||
|
||||
async fn start_language_server(
|
||||
new_server_id: LanguageServerId,
|
||||
http: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
env: Option<HashMap<String, String>>,
|
||||
this: WeakEntity<Self>,
|
||||
|
@ -472,7 +460,7 @@ impl Copilot {
|
|||
cx: &mut AsyncApp,
|
||||
) {
|
||||
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 arguments: Vec<OsString> = vec![server_path.into(), "--stdio".into()];
|
||||
let binary = LanguageServerBinary {
|
||||
|
@ -506,9 +494,23 @@ impl Copilot {
|
|||
let configuration = lsp::DidChangeConfigurationParams {
|
||||
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
|
||||
.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)
|
||||
})?
|
||||
.await?;
|
||||
|
@ -520,16 +522,7 @@ impl Copilot {
|
|||
.await?;
|
||||
|
||||
server
|
||||
.request::<request::SetEditorInfo>(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(),
|
||||
},
|
||||
})
|
||||
.request::<request::SetEditorInfo>(editor_info)
|
||||
.await?;
|
||||
|
||||
anyhow::Ok((server, status))
|
||||
|
@ -668,13 +661,11 @@ impl Copilot {
|
|||
let env = self.build_env(&language_settings.edit_predictions.copilot);
|
||||
let start_task = cx
|
||||
.spawn({
|
||||
let http = self.http.clone();
|
||||
let node_runtime = self.node_runtime.clone();
|
||||
let server_id = self.server_id;
|
||||
async move |this, cx| {
|
||||
clear_copilot_dir().await;
|
||||
Self::start_language_server(server_id, http, node_runtime, env, this, false, cx)
|
||||
.await
|
||||
Self::start_language_server(server_id, node_runtime, env, this, false, cx).await
|
||||
}
|
||||
})
|
||||
.shared();
|
||||
|
@ -1056,73 +1047,31 @@ async fn clear_copilot_config_dir() {
|
|||
remove_matching(copilot_chat::copilot_chat_config_dir(), |_| true).await
|
||||
}
|
||||
|
||||
async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
const SERVER_PATH: &str = "dist/language-server.js";
|
||||
async fn get_copilot_lsp(node_runtime: NodeRuntime) -> anyhow::Result<PathBuf> {
|
||||
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
|
||||
async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
let release =
|
||||
get_release_by_tag_name("zed-industries/copilot", "v0.7.0", http.clone()).await?;
|
||||
let latest_version = node_runtime
|
||||
.npm_package_latest_version(PACKAGE_NAME)
|
||||
.await?;
|
||||
let server_path = paths::copilot_dir().join(SERVER_PATH);
|
||||
|
||||
let version_dir = &paths::copilot_dir().join(format!("copilot-{}", release.tag_name));
|
||||
|
||||
fs::create_dir_all(version_dir).await?;
|
||||
let server_path = version_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
// Copilot LSP looks for this dist dir specifically, so lets add it in.
|
||||
let dist_dir = version_dir.join("dist");
|
||||
fs::create_dir_all(dist_dir.as_path()).await?;
|
||||
|
||||
let url = &release
|
||||
.assets
|
||||
.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)
|
||||
let should_install = node_runtime
|
||||
.should_install_npm_package(
|
||||
PACKAGE_NAME,
|
||||
&server_path,
|
||||
paths::copilot_dir(),
|
||||
&latest_version,
|
||||
)
|
||||
.await;
|
||||
if should_install {
|
||||
node_runtime
|
||||
.npm_install_packages(paths::copilot_dir(), &[(PACKAGE_NAME, &latest_version)])
|
||||
.await?;
|
||||
}
|
||||
|
||||
match fetch_latest(http).await {
|
||||
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
|
||||
}
|
||||
}
|
||||
Ok(server_path)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue