Draft remote prettier formatting

Kirill Bulatov created

Change summary

crates/language/src/buffer.rs   |  4 +-
crates/language/src/proto.rs    | 41 ++++++++++++++++++++++++
crates/prettier/src/prettier.rs | 58 ++++++++++++++++++++++++++++++----
crates/project/src/project.rs   | 35 +++++++++++---------
crates/rpc/proto/zed.proto      | 15 ++++++---
5 files changed, 121 insertions(+), 32 deletions(-)

Detailed changes

crates/language/src/buffer.rs 🔗

@@ -317,8 +317,8 @@ pub struct Chunk<'a> {
 
 pub struct Diff {
     pub(crate) base_version: clock::Global,
-    line_ending: LineEnding,
-    edits: Vec<(Range<usize>, Arc<str>)>,
+    pub(crate) line_ending: LineEnding,
+    pub(crate) edits: Vec<(Range<usize>, Arc<str>)>,
 }
 
 #[derive(Clone, Copy)]

crates/language/src/proto.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
-    Language,
+    Diff, Language,
 };
 use anyhow::{anyhow, Result};
 use clock::ReplicaId;
@@ -587,3 +587,42 @@ pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry
         })
         .collect()
 }
+
+pub fn serialize_diff(diff: Diff) -> proto::Diff {
+    proto::Diff {
+        version: serialize_version(&diff.base_version),
+        line_ending: serialize_line_ending(diff.line_ending) as i32,
+        edits: diff
+            .edits
+            .into_iter()
+            .map(|(range, edit)| proto::DiffEdit {
+                range: Some(proto::Range {
+                    start: range.start as u64,
+                    end: range.end as u64,
+                }),
+                edit: edit.to_string(),
+            })
+            .collect(),
+    }
+}
+
+pub fn deserialize_diff(diff: proto::Diff) -> Diff {
+    Diff {
+        base_version: deserialize_version(&diff.version),
+        line_ending: deserialize_line_ending(
+            rpc::proto::LineEnding::from_i32(diff.line_ending)
+                .unwrap_or_else(|| panic!("invalid line ending {}", diff.line_ending)),
+        ),
+        edits: diff
+            .edits
+            .into_iter()
+            .map(|edit| {
+                let range = edit.range.expect("incorrect edit without a range");
+                (
+                    range.start as usize..range.end as usize,
+                    Arc::from(edit.edit.as_str()),
+                )
+            })
+            .collect(),
+    }
+}

crates/prettier/src/prettier.rs 🔗

@@ -3,11 +3,12 @@ use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
 use anyhow::Context;
-use client::Client;
+use client::{proto, Client};
 use collections::{HashMap, HashSet};
 use fs::Fs;
 use gpui::{AsyncAppContext, ModelHandle};
 use language::language_settings::language_settings;
+use language::proto::deserialize_diff;
 use language::{Buffer, BundledFormatter, Diff};
 use lsp::request::Request;
 use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId};
@@ -28,6 +29,7 @@ pub struct Local {
 }
 
 pub struct Remote {
+    project_id: u64,
     worktree_id: Option<usize>,
     prettier_dir: PathBuf,
     client: Arc<Client>,
@@ -61,8 +63,14 @@ impl Prettier {
         ".editorconfig",
     ];
 
-    pub fn remote(worktree_id: Option<usize>, prettier_dir: PathBuf, client: Arc<Client>) -> Self {
+    pub fn remote(
+        project_id: u64,
+        worktree_id: Option<usize>,
+        prettier_dir: PathBuf,
+        client: Arc<Client>,
+    ) -> Self {
         Self::Remote(Remote {
+            project_id,
             worktree_id,
             prettier_dir,
             client,
@@ -80,7 +88,7 @@ impl Prettier {
                     .components()
                     .into_iter()
                     .take_while(|path_component| {
-                        path_component.as_os_str().to_str() != Some("node_modules")
+                        path_component.as_os_str().to_string_lossy() != "node_modules"
                     })
                     .collect::<PathBuf>();
 
@@ -137,7 +145,7 @@ impl Prettier {
                             for path_component in file_to_format.components().into_iter() {
                                 current_path = current_path.join(path_component);
                                 paths_to_check.push_front(current_path.clone());
-                                if path_component.as_os_str().to_str() == Some("node_modules") {
+                                if path_component.as_os_str().to_string_lossy() == "node_modules" {
                                     break;
                                 }
                             }
@@ -219,14 +227,18 @@ impl Prettier {
 
     pub async fn invoke(
         &self,
-        buffer: &ModelHandle<Buffer>,
+        buffer: Option<&ModelHandle<Buffer>>,
         buffer_path: Option<PathBuf>,
         method: &str,
         cx: &AsyncAppContext,
     ) -> anyhow::Result<Option<Diff>> {
         match method {
             Format::METHOD => self
-                .format(buffer, buffer_path, cx)
+                .format(
+                    buffer.expect("missing buffer for format invocation"),
+                    buffer_path,
+                    cx,
+                )
                 .await
                 .context("invoke method")
                 .map(Some),
@@ -374,7 +386,21 @@ impl Prettier {
                 let diff_task = buffer.read_with(cx, |buffer, cx| buffer.diff(response.text, cx));
                 Ok(diff_task.await)
             }
-            Self::Remote(remote) => todo!("TODO kb"),
+            Self::Remote(remote) => buffer
+                .read_with(cx, |buffer, _| {
+                    remote.client.request(proto::InvokePrettierForBuffer {
+                        buffer_id: Some(buffer.remote_id()),
+                        worktree_id: self.worktree_id().map(|id| id as u64),
+                        method: Format::METHOD.to_string(),
+                        project_id: remote.project_id,
+                        prettier_path: remote.prettier_dir.to_string_lossy().to_string(),
+                    })
+                })
+                .await
+                .context("prettier diff invoke")?
+                .diff
+                .map(deserialize_diff)
+                .context("missing diff after prettier diff invocation"),
         }
     }
 
@@ -385,7 +411,23 @@ impl Prettier {
                 .request::<ClearCache>(())
                 .await
                 .context("prettier clear cache"),
-            Self::Remote(remote) => todo!("TODO kb"),
+            Self::Remote(remote) => remote
+                .client
+                .request(proto::InvokePrettierForBuffer {
+                    buffer_id: None,
+                    worktree_id: self.worktree_id().map(|id| id as u64),
+                    method: ClearCache::METHOD.to_string(),
+                    project_id: remote.project_id,
+                    prettier_path: remote.prettier_dir.to_string_lossy().to_string(),
+                })
+                .await
+                .map(|response| {
+                    debug_assert!(
+                        response.diff.is_none(),
+                        "Cleare cache invocation returned diff data"
+                    )
+                })
+                .context("prettier invoke clear cache"),
         }
     }
 

crates/project/src/project.rs 🔗

@@ -37,7 +37,7 @@ use language::{
     point_to_lsp,
     proto::{
         deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
-        serialize_anchor, serialize_version, split_operations,
+        serialize_anchor, serialize_diff, serialize_version, split_operations,
     },
     range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter,
     CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
@@ -6382,7 +6382,7 @@ impl Project {
             .filter(|(path, _, _)| {
                 !path
                     .components()
-                    .any(|component| component.as_os_str().to_str() == Some("node_modules"))
+                    .any(|component| component.as_os_str().to_string_lossy() == "node_modules")
             })
             .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));
         let current_worktree_id = worktree.read(cx).id();
@@ -8324,14 +8324,14 @@ impl Project {
                 this.prettier_instances
                     .get(&(
                         envelope.payload.worktree_id.map(WorktreeId::from_proto),
-                        PathBuf::from(&envelope.payload.buffer_path),
+                        PathBuf::from(&envelope.payload.prettier_path),
                     ))
                     .cloned()
             })
             .with_context(|| {
                 format!(
-                    "Missing prettier for worktree {:?} and path {}",
-                    envelope.payload.worktree_id, envelope.payload.buffer_path,
+                    "Missing prettier for worktree {:?} and path {:?}",
+                    envelope.payload.worktree_id, envelope.payload.prettier_path,
                 )
             })?
             .await;
@@ -8340,25 +8340,27 @@ impl Project {
             Err(e) => anyhow::bail!("Prettier instance failed to start: {e:#}"),
         };
 
-        let buffer = this
-            .update(&mut cx, |this, cx| {
-                this.opened_buffers
-                    .get(&envelope.payload.buffer_id)
-                    .and_then(|buffer| buffer.upgrade(cx))
-            })
-            .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?;
+        let buffer = this.update(&mut cx, |this, cx| {
+            envelope
+                .payload
+                .buffer_id
+                .and_then(|id| this.opened_buffers.get(&id))
+                .and_then(|buffer| buffer.upgrade(cx))
+        });
 
-        let buffer_path = buffer.read_with(&cx, |buffer, cx| {
-            File::from_dyn(buffer.file()).map(|f| f.full_path(cx))
+        let buffer_path = buffer.as_ref().and_then(|buffer| {
+            buffer.read_with(&cx, |buffer, cx| {
+                File::from_dyn(buffer.file()).map(|f| f.full_path(cx))
+            })
         });
 
         let diff = prettier
-            .invoke(&buffer, buffer_path, &envelope.payload.method, &cx)
+            .invoke(buffer.as_ref(), buffer_path, &envelope.payload.method, &cx)
             .await
             .with_context(|| format!("prettier invoke method {}", &envelope.payload.method))?;
 
         Ok(proto::InvokePrettierForBufferResponse {
-            diff: todo!("TODO kb serialize diff"),
+            diff: diff.map(serialize_diff),
         })
     }
 
@@ -8523,6 +8525,7 @@ impl Project {
                             .map(|prettier_path| {
                                 let prettier_task = Task::ready(
                                     Ok(Arc::new(Prettier::remote(
+                                        project_id,
                                         worktree_id.map(|id| id.to_usize()),
                                         prettier_path.clone(),
                                         client,

crates/rpc/proto/zed.proto 🔗

@@ -1574,8 +1574,8 @@ message PrettierInstanceForBufferResponse {
 
 message InvokePrettierForBuffer {
     uint64 project_id = 1;
-    string buffer_path = 2;
-    uint64 buffer_id = 3;
+    optional uint64 buffer_id = 3;
+    string prettier_path = 2;
     optional uint64 worktree_id = 4;
     string method = 5;
 }
@@ -1585,7 +1585,12 @@ message InvokePrettierForBufferResponse {
 }
 
 message Diff {
-    VectorClockEntry version = 1;
-    string line_ending = 2;
-    string edits = 3;
+    repeated VectorClockEntry version = 1;
+    LineEnding line_ending = 2;
+    repeated DiffEdit edits = 3;
+}
+
+message DiffEdit {
+    Range range = 1;
+    string edit = 2;
 }