1use anyhow::{anyhow, Ok};
2use async_compression::futures::bufread::GzipDecoder;
3use client::Client;
4use gpui::{actions, MutableAppContext};
5use smol::{fs, io::BufReader, stream::StreamExt};
6use std::{env::consts, path::PathBuf, sync::Arc};
7use util::{
8 fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
9};
10
11actions!(copilot, [SignIn]);
12
13pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
14 cx.add_global_action(move |_: &SignIn, cx: &mut MutableAppContext| {
15 Copilot::sign_in(client.http_client(), cx)
16 });
17}
18
19#[derive(Debug)]
20struct Copilot {
21 copilot_server: PathBuf,
22}
23
24impl Copilot {
25 fn sign_in(http: Arc<dyn HttpClient>, cx: &mut MutableAppContext) {
26 let maybe_copilot = cx.default_global::<Option<Arc<Copilot>>>().clone();
27
28 cx.spawn(|mut cx| async move {
29 // Lazily download / initialize copilot LSP
30 let copilot = if let Some(copilot) = maybe_copilot {
31 copilot
32 } else {
33 let copilot_server = get_lsp_binary(http).await?; // TODO: Make this error user visible
34 let new_copilot = Arc::new(Copilot { copilot_server });
35 cx.update({
36 let new_copilot = new_copilot.clone();
37 move |cx| cx.set_global(Some(new_copilot.clone()))
38 });
39 new_copilot
40 };
41
42 dbg!(copilot);
43
44 Ok(())
45 })
46 .detach();
47 }
48}
49
50async fn get_lsp_binary(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
51 ///Check for the latest copilot language server and download it if we haven't already
52 async fn fetch_latest(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
53 let release = latest_github_release("zed-industries/copilotserver", http.clone()).await?;
54 let asset_name = format!("copilot-darwin-{}.gz", consts::ARCH);
55 let asset = release
56 .assets
57 .iter()
58 .find(|asset| asset.name == asset_name)
59 .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
60
61 let destination_path =
62 paths::COPILOT_DIR.join(format!("copilot-{}-{}", release.name, consts::ARCH));
63
64 if fs::metadata(&destination_path).await.is_err() {
65 let mut response = http
66 .get(&asset.browser_download_url, Default::default(), true)
67 .await
68 .map_err(|err| anyhow!("error downloading release: {}", err))?;
69 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
70 let mut file = fs::File::create(&destination_path).await?;
71 futures::io::copy(decompressed_bytes, &mut file).await?;
72 fs::set_permissions(
73 &destination_path,
74 <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
75 )
76 .await?;
77
78 remove_matching(&paths::COPILOT_DIR, |entry| entry != destination_path).await;
79 }
80
81 Ok(destination_path)
82 }
83
84 match fetch_latest(http).await {
85 ok @ Result::Ok(..) => ok,
86 e @ Err(..) => {
87 e.log_err();
88 // Fetch a cached binary, if it exists
89 (|| async move {
90 let mut last = None;
91 let mut entries = fs::read_dir(paths::COPILOT_DIR.as_path()).await?;
92 while let Some(entry) = entries.next().await {
93 last = Some(entry?.path());
94 }
95 last.ok_or_else(|| anyhow!("no cached binary"))
96 })()
97 .await
98 }
99 }
100}