Draft local and remote prettier separation

This commit is contained in:
Kirill Bulatov 2023-09-26 01:53:28 +03:00
parent 6c1c7eaf75
commit faf1d38a6d
7 changed files with 332 additions and 140 deletions

1
Cargo.lock generated
View file

@ -5523,6 +5523,7 @@ name = "prettier"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"client",
"collections", "collections",
"fs", "fs",
"futures 0.3.28", "futures 0.3.28",

View file

@ -7,6 +7,7 @@ edition = "2021"
path = "src/prettier.rs" path = "src/prettier.rs"
[dependencies] [dependencies]
client = { path = "../client" }
collections = { path = "../collections"} collections = { path = "../collections"}
language = { path = "../language" } language = { path = "../language" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }

View file

@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use client::Client;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::Fs; use fs::Fs;
use gpui::{AsyncAppContext, ModelHandle}; use gpui::{AsyncAppContext, ModelHandle};
@ -13,13 +14,24 @@ use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::paths::DEFAULT_PRETTIER_DIR; use util::paths::DEFAULT_PRETTIER_DIR;
pub struct Prettier { pub enum Prettier {
Local(Local),
Remote(Remote),
}
pub struct Local {
worktree_id: Option<usize>, worktree_id: Option<usize>,
default: bool, default: bool,
prettier_dir: PathBuf, prettier_dir: PathBuf,
server: Arc<LanguageServer>, server: Arc<LanguageServer>,
} }
pub struct Remote {
worktree_id: Option<usize>,
prettier_dir: PathBuf,
client: Arc<Client>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct LocateStart { pub struct LocateStart {
pub worktree_root_path: Arc<Path>, pub worktree_root_path: Arc<Path>,
@ -48,6 +60,14 @@ impl Prettier {
".editorconfig", ".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( pub async fn locate(
starting_path: Option<LocateStart>, starting_path: Option<LocateStart>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
@ -188,12 +208,12 @@ impl Prettier {
.spawn(server.initialize(None)) .spawn(server.initialize(None))
.await .await
.context("prettier server initialization")?; .context("prettier server initialization")?;
Ok(Self { Ok(Self::Local(Local {
worktree_id, worktree_id,
server, server,
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
prettier_dir, prettier_dir,
}) }))
} }
pub async fn format( 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:?}"); 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:?}"); 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 plugin_name_into_path = |plugin_name: &str| {
let prettier_plugin_dir = prettier_node_modules.join(plugin_name); let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
@ -278,7 +298,7 @@ impl Prettier {
None => (None, Vec::new()), 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 language_settings = language_settings(buffer_language, buffer.file(), cx);
let mut options = language_settings.prettier.clone(); let mut options = language_settings.prettier.clone();
if !options.contains_key("tabWidth") { if !options.contains_key("tabWidth") {
@ -323,7 +343,8 @@ impl Prettier {
}) })
}).context("prettier params calculation")?; }).context("prettier params calculation")?;
let response = self let response = self
.server .server()
.expect("TODO kb split into local and remote")
.request::<Format>(params) .request::<Format>(params)
.await .await
.context("prettier format request")?; .context("prettier format request")?;
@ -332,26 +353,39 @@ impl Prettier {
} }
pub async fn clear_cache(&self) -> anyhow::Result<()> { pub async fn clear_cache(&self) -> anyhow::Result<()> {
self.server self.server()
.expect("TODO kb split into local and remote")
.request::<ClearCache>(()) .request::<ClearCache>(())
.await .await
.context("prettier clear cache") .context("prettier clear cache")
} }
pub fn server(&self) -> &Arc<LanguageServer> { pub fn server(&self) -> Option<&Arc<LanguageServer>> {
&self.server match self {
Prettier::Local(local) => Some(&local.server),
Prettier::Remote(_) => None,
}
} }
pub fn is_default(&self) -> bool { pub fn is_default(&self) -> bool {
self.default match self {
Prettier::Local(local) => local.default,
Prettier::Remote(_) => false,
}
} }
pub fn prettier_dir(&self) -> &Path { 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> { pub fn worktree_id(&self) -> Option<usize> {
self.worktree_id match self {
Prettier::Local(local) => local.worktree_id,
Prettier::Remote(remote) => remote.worktree_id,
}
} }
} }

View file

@ -130,9 +130,6 @@ async function* readStdin() {
} }
} }
// TODO kb, more methods?
// shutdown
// error
async function handleMessage(message, prettier) { async function handleMessage(message, prettier) {
const { method, id, params } = message; const { method, id, params } = message;
if (method === undefined) { if (method === undefined) {

View file

@ -613,6 +613,8 @@ impl Project {
client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_open_buffer_by_path);
client.add_model_request_handler(Self::handle_save_buffer); client.add_model_request_handler(Self::handle_save_buffer);
client.add_model_message_handler(Self::handle_update_diff_base); 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( pub fn local(
@ -4124,10 +4126,8 @@ impl Project {
if let Some(prettier_task) = this if let Some(prettier_task) = this
.update(&mut cx, |project, cx| { .update(&mut cx, |project, cx| {
project.prettier_instance_for_buffer(buffer, cx) project.prettier_instance_for_buffer(buffer, cx)
}) { }).await {
match prettier_task match prettier_task.await
.await
.await
{ {
Ok(prettier) => { Ok(prettier) => {
let buffer_path = buffer.read_with(&cx, |buffer, cx| { let buffer_path = buffer.read_with(&cx, |buffer, cx| {
@ -4165,10 +4165,8 @@ impl Project {
if let Some(prettier_task) = this if let Some(prettier_task) = this
.update(&mut cx, |project, cx| { .update(&mut cx, |project, cx| {
project.prettier_instance_for_buffer(buffer, cx) project.prettier_instance_for_buffer(buffer, cx)
}) { }).await {
match prettier_task match prettier_task.await
.await
.await
{ {
Ok(prettier) => { Ok(prettier) => {
let buffer_path = buffer.read_with(&cx, |buffer, cx| { 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( fn prettier_instance_for_buffer(
&mut self, &mut self,
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>, 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 = buffer.read(cx);
let buffer_file = buffer.file(); let buffer_file = buffer.file();
let buffer_language = buffer.language()?; let Some(buffer_language) = buffer.language() else {
return Task::ready(None);
};
if !buffer_language if !buffer_language
.lsp_adapters() .lsp_adapters()
.iter() .iter()
.flat_map(|adapter| adapter.enabled_formatters()) .flat_map(|adapter| adapter.enabled_formatters())
.any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. })) .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_file = File::from_dyn(buffer_file);
let buffer_path = buffer_file.map(|file| Arc::clone(file.path())); let buffer_path = buffer_file.map(|file| Arc::clone(file.path()));
let worktree_path = buffer_file let worktree_path = buffer_file
.as_ref() .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)); let worktree_id = buffer_file.map(|file| file.worktree_id(cx));
if self.is_local() || worktree_id.is_none() || worktree_path.is_none() {
let task = cx.spawn(|this, mut cx| async move { let Some(node) = self.node.as_ref().map(Arc::clone) else {
let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs)); return Task::ready(None);
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();
}
}; };
let task = cx.spawn(|this, mut cx| async move {
if let Some(existing_prettier) = this.update(&mut cx, |project, _| { let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs));
project let prettier_dir = match cx
.prettier_instances .background()
.get(&(worktree_id, prettier_dir.clone())) .spawn(Prettier::locate(
.cloned() worktree_path.zip(buffer_path).map(
}) { |(worktree_root_path, starting_path)| LocateStart {
return existing_prettier; worktree_root_path,
} starting_path,
},
log::info!("Found prettier at {prettier_dir:?}, starting."); ),
let task_prettier_dir = prettier_dir.clone(); fs,
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 .await
.context("prettier start") {
.map_err(Arc::new)?; Ok(path) => path,
log::info!("Had started prettier in {:?}", prettier.prettier_dir()); Err(e) => {
return Some(
if let Some(project) = weak_project.upgrade(&mut cx) { Task::ready(Err(Arc::new(e.context(
project.update(&mut cx, |project, cx| { "determining prettier path for worktree {worktree_path:?}",
let name = if prettier.is_default() { ))))
LanguageServerName(Arc::from("prettier (default)")) .shared(),
} 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(),
))),
}
};
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));
});
} }
anyhow::Ok(Arc::new(prettier)).map_err(Arc::new) };
})
.shared(); if let Some(existing_prettier) = this.update(&mut cx, |project, _| {
this.update(&mut cx, |project, _| { project
project .prettier_instances
.prettier_instances .get(&(worktree_id, prettier_dir.clone()))
.insert((worktree_id, prettier_dir), new_prettier_task.clone()); .cloned()
}) {
return Some(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());
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(),
))),
}
};
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)
}); });
new_prettier_task task
}); } else if let Some(project_id) = self.remote_id() {
Some(task) 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
})
}
Err(e) => {
log::error!("Prettier init remote request failed: {e:#}");
None
}
}
});
task
} else {
Task::ready(Some(
Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(),
))
}
} }
fn install_default_formatters( fn install_default_formatters(

View file

@ -170,7 +170,12 @@ message Envelope {
LinkChannel link_channel = 140; LinkChannel link_channel = 140;
UnlinkChannel unlink_channel = 141; 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; uint64 buffer_id = 2;
optional string diff_base = 3; 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;
}

View file

@ -273,6 +273,10 @@ messages!(
(UpdateChannelBufferCollaborators, Foreground), (UpdateChannelBufferCollaborators, Foreground),
(AckBufferOperation, Background), (AckBufferOperation, Background),
(AckChannelMessage, Background), (AckChannelMessage, Background),
(PrettierInstanceForBuffer, Background),
(InvokePrettierForBuffer, Background),
(PrettierInstanceForBufferResponse, Background),
(InvokePrettierForBufferResponse, Background),
); );
request_messages!( request_messages!(
@ -349,7 +353,9 @@ request_messages!(
(UpdateProject, Ack), (UpdateProject, Ack),
(UpdateWorktree, Ack), (UpdateWorktree, Ack),
(JoinChannelBuffer, JoinChannelBufferResponse), (JoinChannelBuffer, JoinChannelBufferResponse),
(LeaveChannelBuffer, Ack) (LeaveChannelBuffer, Ack),
(PrettierInstanceForBuffer, PrettierInstanceForBufferResponse),
(InvokePrettierForBuffer, InvokePrettierForBufferResponse),
); );
entity_messages!( entity_messages!(
@ -400,7 +406,9 @@ entity_messages!(
UpdateProjectCollaborator, UpdateProjectCollaborator,
UpdateWorktree, UpdateWorktree,
UpdateWorktreeSettings, UpdateWorktreeSettings,
UpdateDiffBase UpdateDiffBase,
PrettierInstanceForBuffer,
InvokePrettierForBuffer,
); );
entity_messages!( entity_messages!(