Cargo.lock 🔗
@@ -5523,6 +5523,7 @@ name = "prettier"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"collections",
"fs",
"futures 0.3.28",
Kirill Bulatov created
Cargo.lock | 1
crates/prettier/Cargo.toml | 1
crates/prettier/src/prettier.rs | 58 +++
crates/prettier/src/prettier_server.js | 3
crates/project/src/project.rs | 360 ++++++++++++++++++---------
crates/rpc/proto/zed.proto | 29 ++
crates/rpc/src/proto.rs | 12
7 files changed, 328 insertions(+), 136 deletions(-)
@@ -5523,6 +5523,7 @@ name = "prettier"
version = "0.1.0"
dependencies = [
"anyhow",
+ "client",
"collections",
"fs",
"futures 0.3.28",
@@ -7,6 +7,7 @@ edition = "2021"
path = "src/prettier.rs"
[dependencies]
+client = { path = "../client" }
collections = { path = "../collections"}
language = { path = "../language" }
gpui = { path = "../gpui" }
@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::Context;
+use client::Client;
use collections::{HashMap, HashSet};
use fs::Fs;
use gpui::{AsyncAppContext, ModelHandle};
@@ -13,13 +14,24 @@ use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize};
use util::paths::DEFAULT_PRETTIER_DIR;
-pub struct Prettier {
+pub enum Prettier {
+ Local(Local),
+ Remote(Remote),
+}
+
+pub struct Local {
worktree_id: Option<usize>,
default: bool,
prettier_dir: PathBuf,
server: Arc<LanguageServer>,
}
+pub struct Remote {
+ worktree_id: Option<usize>,
+ prettier_dir: PathBuf,
+ client: Arc<Client>,
+}
+
#[derive(Debug)]
pub struct LocateStart {
pub worktree_root_path: Arc<Path>,
@@ -48,6 +60,14 @@ impl Prettier {
".editorconfig",
];
+ pub fn remote(worktree_id: Option<usize>, prettier_dir: PathBuf, client: Arc<Client>) -> Self {
+ Self::Remote(Remote {
+ worktree_id,
+ prettier_dir,
+ client,
+ })
+ }
+
pub async fn locate(
starting_path: Option<LocateStart>,
fs: Arc<dyn Fs>,
@@ -188,12 +208,12 @@ impl Prettier {
.spawn(server.initialize(None))
.await
.context("prettier server initialization")?;
- Ok(Self {
+ Ok(Self::Local(Local {
worktree_id,
server,
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
prettier_dir,
- })
+ }))
}
pub async fn format(
@@ -239,7 +259,7 @@ impl Prettier {
log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}");
}
- let prettier_node_modules = self.prettier_dir.join("node_modules");
+ let prettier_node_modules = self.prettier_dir().join("node_modules");
anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}");
let plugin_name_into_path = |plugin_name: &str| {
let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
@@ -278,7 +298,7 @@ impl Prettier {
None => (None, Vec::new()),
};
- let prettier_options = if self.default {
+ let prettier_options = if self.is_default() {
let language_settings = language_settings(buffer_language, buffer.file(), cx);
let mut options = language_settings.prettier.clone();
if !options.contains_key("tabWidth") {
@@ -323,7 +343,8 @@ impl Prettier {
})
}).context("prettier params calculation")?;
let response = self
- .server
+ .server()
+ .expect("TODO kb split into local and remote")
.request::<Format>(params)
.await
.context("prettier format request")?;
@@ -332,26 +353,39 @@ impl Prettier {
}
pub async fn clear_cache(&self) -> anyhow::Result<()> {
- self.server
+ self.server()
+ .expect("TODO kb split into local and remote")
.request::<ClearCache>(())
.await
.context("prettier clear cache")
}
- pub fn server(&self) -> &Arc<LanguageServer> {
- &self.server
+ pub fn server(&self) -> Option<&Arc<LanguageServer>> {
+ match self {
+ Prettier::Local(local) => Some(&local.server),
+ Prettier::Remote(_) => None,
+ }
}
pub fn is_default(&self) -> bool {
- self.default
+ match self {
+ Prettier::Local(local) => local.default,
+ Prettier::Remote(_) => false,
+ }
}
pub fn prettier_dir(&self) -> &Path {
- &self.prettier_dir
+ match self {
+ Prettier::Local(local) => &local.prettier_dir,
+ Prettier::Remote(remote) => &remote.prettier_dir,
+ }
}
pub fn worktree_id(&self) -> Option<usize> {
- self.worktree_id
+ match self {
+ Prettier::Local(local) => local.worktree_id,
+ Prettier::Remote(remote) => remote.worktree_id,
+ }
}
}
@@ -130,9 +130,6 @@ async function* readStdin() {
}
}
-// TODO kb, more methods?
-// shutdown
-// error
async function handleMessage(message, prettier) {
const { method, id, params } = message;
if (method === undefined) {
@@ -613,6 +613,8 @@ impl Project {
client.add_model_request_handler(Self::handle_open_buffer_by_path);
client.add_model_request_handler(Self::handle_save_buffer);
client.add_model_message_handler(Self::handle_update_diff_base);
+ client.add_model_request_handler(Self::handle_prettier_instance_for_buffer);
+ client.add_model_request_handler(Self::handle_invoke_prettier);
}
pub fn local(
@@ -4124,10 +4126,8 @@ impl Project {
if let Some(prettier_task) = this
.update(&mut cx, |project, cx| {
project.prettier_instance_for_buffer(buffer, cx)
- }) {
- match prettier_task
- .await
- .await
+ }).await {
+ match prettier_task.await
{
Ok(prettier) => {
let buffer_path = buffer.read_with(&cx, |buffer, cx| {
@@ -4165,10 +4165,8 @@ impl Project {
if let Some(prettier_task) = this
.update(&mut cx, |project, cx| {
project.prettier_instance_for_buffer(buffer, cx)
- }) {
- match prettier_task
- .await
- .await
+ }).await {
+ match prettier_task.await
{
Ok(prettier) => {
let buffer_path = buffer.read_with(&cx, |buffer, cx| {
@@ -8288,143 +8286,269 @@ impl Project {
}
}
+ async fn handle_prettier_instance_for_buffer(
+ this: ModelHandle<Self>,
+ envelope: TypedEnvelope<proto::PrettierInstanceForBuffer>,
+ _: Arc<Client>,
+ mut cx: AsyncAppContext,
+ ) -> anyhow::Result<proto::PrettierInstanceForBufferResponse> {
+ let prettier_instance_for_buffer_task = this.update(&mut cx, |this, cx| {
+ let buffer = this
+ .opened_buffers
+ .get(&envelope.payload.buffer_id)
+ .and_then(|buffer| buffer.upgrade(cx))
+ .with_context(|| format!("unknown buffer id {}", envelope.payload.buffer_id))?;
+ anyhow::Ok(this.prettier_instance_for_buffer(&buffer, cx))
+ })?;
+
+ let prettier_path = match prettier_instance_for_buffer_task.await {
+ Some(prettier) => match prettier.await {
+ Ok(prettier) => Some(prettier.prettier_dir().display().to_string()),
+ Err(e) => {
+ anyhow::bail!("Failed to create prettier instance for remote request: {e:#}")
+ }
+ },
+ None => None,
+ };
+ Ok(proto::PrettierInstanceForBufferResponse { prettier_path })
+ }
+
+ async fn handle_invoke_prettier(
+ this: ModelHandle<Self>,
+ envelope: TypedEnvelope<proto::InvokePrettierForBuffer>,
+ _: Arc<Client>,
+ mut cx: AsyncAppContext,
+ ) -> anyhow::Result<proto::InvokePrettierForBufferResponse> {
+ let prettier = this
+ .read_with(&cx, |this, _| {
+ this.prettier_instances
+ .get(&(
+ envelope.payload.worktree_id.map(WorktreeId::from_proto),
+ PathBuf::from(&envelope.payload.buffer_path),
+ ))
+ .cloned()
+ })
+ .with_context(|| {
+ format!(
+ "Missing prettier for worktree {:?} and path {}",
+ envelope.payload.worktree_id, envelope.payload.buffer_path,
+ )
+ })?
+ .await;
+ let prettier = match prettier {
+ Ok(prettier) => prettier,
+ 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_path = buffer.read_with(&cx, |buffer, cx| {
+ File::from_dyn(buffer.file()).map(|f| f.full_path(cx))
+ });
+
+ let diff = prettier
+ .format(&buffer, buffer_path, &cx)
+ .await
+ .context("handle buffer formatting")?;
+ todo!("TODO kb serialize diff")
+ }
+
fn prettier_instance_for_buffer(
&mut self,
buffer: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
- ) -> Option<Task<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>> {
+ ) -> Task<Option<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>> {
let buffer = buffer.read(cx);
let buffer_file = buffer.file();
- let buffer_language = buffer.language()?;
+ let Some(buffer_language) = buffer.language() else {
+ return Task::ready(None);
+ };
if !buffer_language
.lsp_adapters()
.iter()
.flat_map(|adapter| adapter.enabled_formatters())
.any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. }))
{
- return None;
+ return Task::ready(None);
}
- let node = Arc::clone(self.node.as_ref()?);
let buffer_file = File::from_dyn(buffer_file);
let buffer_path = buffer_file.map(|file| Arc::clone(file.path()));
let worktree_path = buffer_file
.as_ref()
- .map(|file| file.worktree.read(cx).abs_path());
+ .and_then(|file| Some(file.worktree.read(cx).abs_path()));
let worktree_id = buffer_file.map(|file| file.worktree_id(cx));
+ if self.is_local() || worktree_id.is_none() || worktree_path.is_none() {
+ let Some(node) = self.node.as_ref().map(Arc::clone) else {
+ return Task::ready(None);
+ };
+ let task = cx.spawn(|this, mut cx| async move {
+ let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs));
+ let prettier_dir = match cx
+ .background()
+ .spawn(Prettier::locate(
+ worktree_path.zip(buffer_path).map(
+ |(worktree_root_path, starting_path)| LocateStart {
+ worktree_root_path,
+ starting_path,
+ },
+ ),
+ fs,
+ ))
+ .await
+ {
+ Ok(path) => path,
+ Err(e) => {
+ return Some(
+ Task::ready(Err(Arc::new(e.context(
+ "determining prettier path for worktree {worktree_path:?}",
+ ))))
+ .shared(),
+ );
+ }
+ };
- let task = cx.spawn(|this, mut cx| async move {
- let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs));
- let prettier_dir = match cx
- .background()
- .spawn(Prettier::locate(
- worktree_path
- .zip(buffer_path)
- .map(|(worktree_root_path, starting_path)| LocateStart {
- worktree_root_path,
- starting_path,
- }),
- fs,
- ))
- .await
- {
- Ok(path) => path,
- Err(e) => {
- return Task::Ready(Some(Result::Err(Arc::new(
- e.context("determining prettier path for worktree {worktree_path:?}"),
- ))))
- .shared();
+ if let Some(existing_prettier) = this.update(&mut cx, |project, _| {
+ project
+ .prettier_instances
+ .get(&(worktree_id, prettier_dir.clone()))
+ .cloned()
+ }) {
+ return Some(existing_prettier);
}
- };
- if let Some(existing_prettier) = this.update(&mut cx, |project, _| {
- project
- .prettier_instances
- .get(&(worktree_id, prettier_dir.clone()))
- .cloned()
- }) {
- return existing_prettier;
- }
+ log::info!("Found prettier at {prettier_dir:?}, starting.");
+ let task_prettier_dir = prettier_dir.clone();
+ let weak_project = this.downgrade();
+ let new_server_id =
+ this.update(&mut cx, |this, _| this.languages.next_language_server_id());
+ let new_prettier_task = cx
+ .spawn(|mut cx| async move {
+ let prettier = Prettier::start(
+ worktree_id.map(|id| id.to_usize()),
+ new_server_id,
+ task_prettier_dir,
+ node,
+ cx.clone(),
+ )
+ .await
+ .context("prettier start")
+ .map_err(Arc::new)?;
+ log::info!("Had started prettier in {:?}", prettier.prettier_dir());
- log::info!("Found prettier at {prettier_dir:?}, starting.");
- let task_prettier_dir = prettier_dir.clone();
- let weak_project = this.downgrade();
- let new_server_id =
- this.update(&mut cx, |this, _| this.languages.next_language_server_id());
- let new_prettier_task = cx
- .spawn(|mut cx| async move {
- let prettier = Prettier::start(
- worktree_id.map(|id| id.to_usize()),
- new_server_id,
- task_prettier_dir,
- node,
- cx.clone(),
- )
- .await
- .context("prettier start")
- .map_err(Arc::new)?;
- log::info!("Had started prettier in {:?}", prettier.prettier_dir());
-
- if let Some(project) = weak_project.upgrade(&mut cx) {
- project.update(&mut cx, |project, cx| {
- let name = if prettier.is_default() {
- LanguageServerName(Arc::from("prettier (default)"))
- } else {
- let prettier_dir = prettier.prettier_dir();
- let worktree_path = prettier
- .worktree_id()
- .map(WorktreeId::from_usize)
- .and_then(|id| project.worktree_for_id(id, cx))
- .map(|worktree| worktree.read(cx).abs_path());
- match worktree_path {
- Some(worktree_path) => {
- if worktree_path.as_ref() == prettier_dir {
- LanguageServerName(Arc::from(format!(
- "prettier ({})",
- prettier_dir
- .file_name()
- .and_then(|name| name.to_str())
- .unwrap_or_default()
- )))
- } else {
- let dir_to_display = match prettier_dir
- .strip_prefix(&worktree_path)
- .ok()
- {
- Some(relative_path) => relative_path,
- None => prettier_dir,
- };
- LanguageServerName(Arc::from(format!(
- "prettier ({})",
- dir_to_display.display(),
- )))
+ if let Some((project, prettier_server)) =
+ weak_project.upgrade(&mut cx).zip(prettier.server())
+ {
+ project.update(&mut cx, |project, cx| {
+ let name = if prettier.is_default() {
+ LanguageServerName(Arc::from("prettier (default)"))
+ } else {
+ let prettier_dir = prettier.prettier_dir();
+ let worktree_path = prettier
+ .worktree_id()
+ .map(WorktreeId::from_usize)
+ .and_then(|id| project.worktree_for_id(id, cx))
+ .map(|worktree| worktree.read(cx).abs_path());
+ match worktree_path {
+ Some(worktree_path) => {
+ if worktree_path.as_ref() == prettier_dir {
+ LanguageServerName(Arc::from(format!(
+ "prettier ({})",
+ prettier_dir
+ .file_name()
+ .and_then(|name| name.to_str())
+ .unwrap_or_default()
+ )))
+ } else {
+ let dir_to_display = match prettier_dir
+ .strip_prefix(&worktree_path)
+ .ok()
+ {
+ Some(relative_path) => relative_path,
+ None => prettier_dir,
+ };
+ LanguageServerName(Arc::from(format!(
+ "prettier ({})",
+ dir_to_display.display(),
+ )))
+ }
}
+ None => LanguageServerName(Arc::from(format!(
+ "prettier ({})",
+ prettier_dir.display(),
+ ))),
}
- None => LanguageServerName(Arc::from(format!(
- "prettier ({})",
- prettier_dir.display(),
- ))),
- }
- };
- project
- .supplementary_language_servers
- .insert(new_server_id, (name, Arc::clone(prettier.server())));
- // TODO kb could there be a race with multiple default prettier instances added?
- // also, clean up prettiers for dropped workspaces (e.g. external files that got closed)
- cx.emit(Event::LanguageServerAdded(new_server_id));
- });
+ };
+
+ project
+ .supplementary_language_servers
+ .insert(new_server_id, (name, Arc::clone(prettier_server)));
+ // TODO kb could there be a race with multiple default prettier instances added?
+ // also, clean up prettiers for dropped workspaces (e.g. external files that got closed)
+ cx.emit(Event::LanguageServerAdded(new_server_id));
+ });
+ }
+ Ok(Arc::new(prettier)).map_err(Arc::new)
+ })
+ .shared();
+ this.update(&mut cx, |project, _| {
+ project
+ .prettier_instances
+ .insert((worktree_id, prettier_dir), new_prettier_task.clone());
+ });
+ Some(new_prettier_task)
+ });
+ task
+ } else if let Some(project_id) = self.remote_id() {
+ let client = self.client.clone();
+ let request = proto::PrettierInstanceForBuffer {
+ project_id,
+ buffer_id: buffer.remote_id(),
+ };
+ let task = cx.spawn(|this, mut cx| async move {
+ match client.request(request).await {
+ Ok(response) => {
+ response
+ .prettier_path
+ .map(PathBuf::from)
+ .map(|prettier_path| {
+ let prettier_task = Task::ready(
+ Ok(Arc::new(Prettier::remote(
+ worktree_id.map(|id| id.to_usize()),
+ prettier_path.clone(),
+ client,
+ )))
+ .map_err(Arc::new),
+ )
+ .shared();
+ this.update(&mut cx, |project, _| {
+ project.prettier_instances.insert(
+ (worktree_id, prettier_path),
+ prettier_task.clone(),
+ );
+ });
+ prettier_task
+ })
}
- anyhow::Ok(Arc::new(prettier)).map_err(Arc::new)
- })
- .shared();
- this.update(&mut cx, |project, _| {
- project
- .prettier_instances
- .insert((worktree_id, prettier_dir), new_prettier_task.clone());
+ Err(e) => {
+ log::error!("Prettier init remote request failed: {e:#}");
+ None
+ }
+ }
});
- new_prettier_task
- });
- Some(task)
+
+ task
+ } else {
+ Task::ready(Some(
+ Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(),
+ ))
+ }
}
fn install_default_formatters(
@@ -170,7 +170,12 @@ message Envelope {
LinkChannel link_channel = 140;
UnlinkChannel unlink_channel = 141;
- MoveChannel move_channel = 142; // current max: 144
+ MoveChannel move_channel = 142;
+
+ PrettierInstanceForBuffer prettier_instance_for_buffer = 145;
+ PrettierInstanceForBufferResponse prettier_instance_for_buffer_response = 146;
+ InvokePrettierForBuffer invoke_prettier_for_buffer = 147;
+ InvokePrettierForBufferResponse invoke_prettier_for_buffer_response = 148; // Current max: 148
}
}
@@ -1557,3 +1562,25 @@ message UpdateDiffBase {
uint64 buffer_id = 2;
optional string diff_base = 3;
}
+
+message PrettierInstanceForBuffer {
+ uint64 project_id = 1;
+ uint64 buffer_id = 2;
+}
+
+message PrettierInstanceForBufferResponse {
+ optional string prettier_path = 1;
+}
+
+message InvokePrettierForBuffer {
+ uint64 project_id = 1;
+ string buffer_path = 2;
+ uint64 buffer_id = 3;
+ optional uint64 worktree_id = 4;
+ string method = 5;
+ optional string command_parameters = 6;
+}
+
+message InvokePrettierForBufferResponse {
+ optional string diff = 1;
+}
@@ -273,6 +273,10 @@ messages!(
(UpdateChannelBufferCollaborators, Foreground),
(AckBufferOperation, Background),
(AckChannelMessage, Background),
+ (PrettierInstanceForBuffer, Background),
+ (InvokePrettierForBuffer, Background),
+ (PrettierInstanceForBufferResponse, Background),
+ (InvokePrettierForBufferResponse, Background),
);
request_messages!(
@@ -349,7 +353,9 @@ request_messages!(
(UpdateProject, Ack),
(UpdateWorktree, Ack),
(JoinChannelBuffer, JoinChannelBufferResponse),
- (LeaveChannelBuffer, Ack)
+ (LeaveChannelBuffer, Ack),
+ (PrettierInstanceForBuffer, PrettierInstanceForBufferResponse),
+ (InvokePrettierForBuffer, InvokePrettierForBufferResponse),
);
entity_messages!(
@@ -400,7 +406,9 @@ entity_messages!(
UpdateProjectCollaborator,
UpdateWorktree,
UpdateWorktreeSettings,
- UpdateDiffBase
+ UpdateDiffBase,
+ PrettierInstanceForBuffer,
+ InvokePrettierForBuffer,
);
entity_messages!(