Start on integrating rust-analyzer

Antonio Scandurra , Nathan Sobo , and Max Brunsfeld created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>

Change summary

Cargo.lock                      |  40 ++++++
crates/gpui/src/executor.rs     |   1 
crates/lsp/Cargo.toml           |  15 ++
crates/lsp/src/lib.rs           | 201 +++++++++++++++++++++++++++++++++++
crates/project/Cargo.toml       |   7 
crates/project/src/lib.rs       |  18 ++
crates/project_panel/src/lib.rs |   3 
crates/workspace/src/lib.rs     |   3 
8 files changed, 279 insertions(+), 9 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2949,6 +2949,34 @@ dependencies = [
  "scoped-tls",
 ]
 
+[[package]]
+name = "lsp"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "futures",
+ "gpui",
+ "lsp-types",
+ "parking_lot",
+ "serde 1.0.125",
+ "serde_json 1.0.64",
+ "smol",
+ "util",
+]
+
+[[package]]
+name = "lsp-types"
+version = "0.91.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be7801b458592d0998af808d97f6a85a6057af3aaf2a2a5c3c677702bbeb4ed7"
+dependencies = [
+ "bitflags 1.2.1",
+ "serde 1.0.125",
+ "serde_json 1.0.64",
+ "serde_repr",
+ "url",
+]
+
 [[package]]
 name = "lzw"
 version = "0.10.0"
@@ -3762,6 +3790,7 @@ dependencies = [
  "lazy_static",
  "libc",
  "log",
+ "lsp",
  "parking_lot",
  "postage",
  "rand 0.8.3",
@@ -4571,6 +4600,17 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "serde_repr"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "serde_urlencoded"
 version = "0.7.0"

crates/gpui/src/executor.rs 🔗

@@ -50,6 +50,7 @@ type AnyFuture = Pin<Box<dyn 'static + Send + Future<Output = Box<dyn Any + Send
 type AnyTask = async_task::Task<Box<dyn Any + Send + 'static>>;
 type AnyLocalTask = async_task::Task<Box<dyn Any + 'static>>;
 
+#[must_use]
 pub enum Task<T> {
     Local {
         any_task: AnyLocalTask,

crates/lsp/Cargo.toml 🔗

@@ -0,0 +1,15 @@
+[package]
+name = "lsp"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+gpui = { path = "../gpui" }
+util = { path = "../util" }
+anyhow = "1.0"
+futures = "0.3"
+lsp-types = "0.91"
+parking_lot = "0.11"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = { version = "1.0", features = ["raw_value"] }
+smol = "1.2"

crates/lsp/src/lib.rs 🔗

@@ -0,0 +1,201 @@
+use anyhow::{anyhow, Context, Result};
+use gpui::{executor, Task};
+use parking_lot::Mutex;
+use serde::{Deserialize, Serialize};
+use serde_json::value::RawValue;
+use smol::{
+    channel,
+    io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
+    process::Command,
+};
+use std::{
+    collections::HashMap,
+    future::Future,
+    io::Write,
+    sync::{
+        atomic::{AtomicUsize, Ordering::SeqCst},
+        Arc,
+    },
+};
+use std::{path::Path, process::Stdio};
+use util::TryFutureExt;
+
+const JSON_RPC_VERSION: &'static str = "2.0";
+const CONTENT_LEN_HEADER: &'static str = "Content-Length: ";
+
+pub struct LanguageServer {
+    next_id: AtomicUsize,
+    outbound_tx: channel::Sender<Vec<u8>>,
+    response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
+    _input_task: Task<Option<()>>,
+    _output_task: Task<Option<()>>,
+}
+
+type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
+
+#[derive(Serialize)]
+struct Request<T> {
+    jsonrpc: &'static str,
+    id: usize,
+    method: &'static str,
+    params: T,
+}
+
+#[derive(Deserialize)]
+struct Error {
+    message: String,
+}
+
+#[derive(Deserialize)]
+struct Notification<'a> {
+    method: String,
+    #[serde(borrow)]
+    params: &'a RawValue,
+}
+
+#[derive(Deserialize)]
+struct Response<'a> {
+    id: usize,
+    #[serde(default)]
+    error: Option<Error>,
+    #[serde(default, borrow)]
+    result: Option<&'a RawValue>,
+}
+
+impl LanguageServer {
+    pub fn new(path: &Path, background: &executor::Background) -> Result<Arc<Self>> {
+        let mut server = Command::new(path)
+            .stdin(Stdio::piped())
+            .stdout(Stdio::piped())
+            .stderr(Stdio::inherit())
+            .spawn()?;
+        let mut stdin = server.stdin.take().unwrap();
+        let mut stdout = BufReader::new(server.stdout.take().unwrap());
+        let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
+        let response_handlers = Arc::new(Mutex::new(HashMap::<usize, ResponseHandler>::new()));
+        let _input_task = background.spawn(
+            {
+                let response_handlers = response_handlers.clone();
+                async move {
+                    let mut buffer = Vec::new();
+                    loop {
+                        buffer.clear();
+
+                        stdout.read_until(b'\n', &mut buffer).await?;
+                        stdout.read_until(b'\n', &mut buffer).await?;
+                        let message_len: usize = std::str::from_utf8(&buffer)?
+                            .strip_prefix(CONTENT_LEN_HEADER)
+                            .ok_or_else(|| anyhow!("invalid header"))?
+                            .trim_end()
+                            .parse()?;
+
+                        buffer.resize(message_len, 0);
+                        stdout.read_exact(&mut buffer).await?;
+                        if let Ok(Notification { .. }) = serde_json::from_slice(&buffer) {
+                        } else if let Ok(Response { id, error, result }) =
+                            serde_json::from_slice(&buffer)
+                        {
+                            if let Some(handler) = response_handlers.lock().remove(&id) {
+                                if let Some(result) = result {
+                                    handler(Ok(result.get()));
+                                } else if let Some(error) = error {
+                                    handler(Err(error));
+                                }
+                            }
+                        } else {
+                            return Err(anyhow!(
+                                "failed to deserialize message:\n{}",
+                                std::str::from_utf8(&buffer)?
+                            ));
+                        }
+                    }
+                }
+            }
+            .log_err(),
+        );
+        let _output_task = background.spawn(
+            async move {
+                let mut content_len_buffer = Vec::new();
+                loop {
+                    let message = outbound_rx.recv().await?;
+                    write!(content_len_buffer, "{}", message.len()).unwrap();
+                    stdin.write_all(CONTENT_LEN_HEADER.as_bytes()).await?;
+                    stdin.write_all(&content_len_buffer).await?;
+                    stdin.write_all("\r\n\r\n".as_bytes()).await?;
+                    stdin.write_all(&message).await?;
+                }
+            }
+            .log_err(),
+        );
+
+        let this = Arc::new(Self {
+            response_handlers,
+            next_id: Default::default(),
+            outbound_tx,
+            _input_task,
+            _output_task,
+        });
+        let init = this.clone().init();
+        background
+            .spawn(async move {
+                init.log_err().await;
+            })
+            .detach();
+
+        Ok(this)
+    }
+
+    async fn init(self: Arc<Self>) -> Result<()> {
+        let init_response = self
+            .request::<lsp_types::request::Initialize>(lsp_types::InitializeParams {
+                process_id: Default::default(),
+                root_path: Default::default(),
+                root_uri: Default::default(),
+                initialization_options: Default::default(),
+                capabilities: Default::default(),
+                trace: Default::default(),
+                workspace_folders: Default::default(),
+                client_info: Default::default(),
+                locale: Default::default(),
+            })
+            .await?;
+        Ok(())
+    }
+
+    pub fn request<T: lsp_types::request::Request>(
+        self: &Arc<Self>,
+        params: T::Params,
+    ) -> impl Future<Output = Result<T::Result>>
+    where
+        T::Result: 'static + Send,
+    {
+        let id = self.next_id.fetch_add(1, SeqCst);
+        let message = serde_json::to_vec(&Request {
+            jsonrpc: JSON_RPC_VERSION,
+            id,
+            method: T::METHOD,
+            params,
+        })
+        .unwrap();
+        let mut response_handlers = self.response_handlers.lock();
+        let (tx, rx) = smol::channel::bounded(1);
+        response_handlers.insert(
+            id,
+            Box::new(move |result| {
+                let response = match result {
+                    Ok(response) => {
+                        serde_json::from_str(response).context("failed to deserialize response")
+                    }
+                    Err(error) => Err(anyhow!("{}", error.message)),
+                };
+                let _ = smol::block_on(tx.send(response));
+            }),
+        );
+
+        let outbound_tx = self.outbound_tx.clone();
+        async move {
+            outbound_tx.send(message).await?;
+            rx.recv().await?
+        }
+    }
+}

crates/project/Cargo.toml 🔗

@@ -8,15 +8,15 @@ test-support = []
 
 [dependencies]
 buffer = { path = "../buffer" }
+client = { path = "../client" }
 clock = { path = "../clock" }
 fsevent = { path = "../fsevent" }
 fuzzy = { path = "../fuzzy" }
 gpui = { path = "../gpui" }
-client = { path = "../client" }
+lsp = { path = "../lsp" }
+rpc = { path = "../rpc" }
 sum_tree = { path = "../sum_tree" }
 util = { path = "../util" }
-rpc = { path = "../rpc" }
-
 anyhow = "1.0.38"
 async-trait = "0.1"
 futures = "0.3"
@@ -35,6 +35,5 @@ toml = "0.5"
 client = { path = "../client", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 rpc = { path = "../rpc", features = ["test-support"] }
-
 rand = "0.8.3"
 tempdir = { version = "0.3.7" }

crates/project/src/lib.rs 🔗

@@ -7,7 +7,8 @@ use buffer::LanguageRegistry;
 use client::Client;
 use futures::Future;
 use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
-use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
+use gpui::{executor, AppContext, Entity, ModelContext, ModelHandle, Task};
+use lsp::LanguageServer;
 use std::{
     path::Path,
     sync::{atomic::AtomicBool, Arc},
@@ -23,6 +24,7 @@ pub struct Project {
     languages: Arc<LanguageRegistry>,
     client: Arc<client::Client>,
     fs: Arc<dyn Fs>,
+    language_server: Arc<LanguageServer>,
 }
 
 pub enum Event {
@@ -43,13 +45,23 @@ pub struct ProjectEntry {
 }
 
 impl Project {
-    pub fn new(languages: Arc<LanguageRegistry>, rpc: Arc<Client>, fs: Arc<dyn Fs>) -> Self {
+    pub fn new(
+        languages: Arc<LanguageRegistry>,
+        rpc: Arc<Client>,
+        fs: Arc<dyn Fs>,
+        background: &executor::Background,
+    ) -> Self {
         Self {
             worktrees: Default::default(),
             active_entry: None,
             languages,
             client: rpc,
             fs,
+            language_server: LanguageServer::new(
+                Path::new("/Users/as-cii/Downloads/rust-analyzer-x86_64-apple-darwin"),
+                background,
+            )
+            .unwrap(),
         }
     }
 
@@ -408,6 +420,6 @@ mod tests {
         let languages = Arc::new(LanguageRegistry::new());
         let fs = Arc::new(RealFs);
         let rpc = client::Client::new();
-        cx.add_model(|_| Project::new(languages, rpc, fs))
+        cx.add_model(|cx| Project::new(languages, rpc, fs, cx.background()))
     }
 }

crates/project_panel/src/lib.rs 🔗

@@ -617,11 +617,12 @@ mod tests {
         )
         .await;
 
-        let project = cx.add_model(|_| {
+        let project = cx.add_model(|cx| {
             Project::new(
                 params.languages.clone(),
                 params.client.clone(),
                 params.fs.clone(),
+                cx.background(),
             )
         });
         let root1 = project

crates/workspace/src/lib.rs 🔗

@@ -322,11 +322,12 @@ pub struct Workspace {
 
 impl Workspace {
     pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
-        let project = cx.add_model(|_| {
+        let project = cx.add_model(|cx| {
             Project::new(
                 params.languages.clone(),
                 params.client.clone(),
                 params.fs.clone(),
+                cx.background(),
             )
         });
         cx.observe(&project, |_, _, cx| cx.notify()).detach();