1use serde::de::DeserializeOwned;
2use std::cell::RefCell;
3use std::io::BufRead;
4use std::io::ErrorKind;
5use std::io::Read;
6use std::io::Write;
7use std::path::Path;
8use std::path::PathBuf;
9use std::process::Child;
10use std::process::ChildStderr;
11use std::process::Command;
12use std::process::Stdio;
13use std::rc::Rc;
14use std::sync::Arc;
15use std::time::Duration;
16use tokio::sync::oneshot;
17use tokio_util::sync::CancellationToken;
18
19use super::PLUGIN_SCHEMA_VERSION;
20use super::messages::CheckConfigUpdatesMessageBody;
21use super::messages::CheckConfigUpdatesResponseBody;
22use super::messages::FormatMessageBody;
23use super::messages::HostFormatMessageBody;
24use super::messages::MessageBody;
25use super::messages::ProcessPluginMessage;
26use super::messages::RegisterConfigMessageBody;
27use super::messages::ResponseBody;
28use crate::async_runtime::DropGuardAction;
29use crate::async_runtime::LocalBoxFuture;
30use crate::communication::AtomicFlag;
31use crate::communication::IdGenerator;
32use crate::communication::MessageReader;
33use crate::communication::MessageWriter;
34use crate::communication::RcIdStore;
35use crate::communication::SingleThreadMessageWriter;
36use crate::configuration::ConfigKeyMap;
37use crate::configuration::ConfigurationDiagnostic;
38use crate::configuration::GlobalConfiguration;
39use crate::plugins::ConfigChange;
40use crate::plugins::CriticalFormatError;
41use crate::plugins::FILE_MATCHING_INFO_ERROR_MESSAGE;
42use crate::plugins::FileMatchingInfo;
43use crate::plugins::FormatConfigId;
44use crate::plugins::FormatError;
45use crate::plugins::FormatRange;
46use crate::plugins::FormatResult;
47use crate::plugins::HostFormatRequest;
48use crate::plugins::NullCancellationToken;
49use crate::plugins::PluginInfo;
50use crate::plugins::error_to_string;
51
52type Result<T> = std::result::Result<T, FormatError>;
53
54type DprintCancellationToken = Arc<dyn super::super::CancellationToken>;
55
56#[derive(Debug, thiserror::Error)]
58enum SchemaVersionError {
59 #[error("Failed asking for schema version: {0}")]
60 Ask(std::io::Error),
61 #[error("Failed flushing schema version request: {0}")]
62 Flush(std::io::Error),
63 #[error("Could not read success response: {0}")]
64 ReadAcknowledgement(std::io::Error),
65 #[error("Plugin response was unexpected ({0}).")]
66 UnexpectedAcknowledgement(u32),
67 #[error("Could not read schema version: {0}")]
68 ReadVersion(std::io::Error),
69}
70
71#[derive(Debug, thiserror::Error)]
73enum CommunicatorError {
74 #[error("Error starting {executable} with args [{args}]. {error}")]
75 StartProcess { executable: String, args: String, error: std::io::Error },
76 #[error("Failed plugin schema verification. This may indicate you are using an old version of the dprint CLI or plugin and should upgrade")]
77 SchemaVerification(#[from] SchemaVersionError),
78 #[error(
79 "This plugin is too old to run in the dprint CLI and you will need to manually upgrade it (version was {actual}, but expected {expected}).\n\nUpgrade instructions: https://github.com/dprint/dprint/issues/731"
80 )]
81 PluginTooOld { actual: u32, expected: u32 },
82 #[error("Your dprint CLI is too old to run this plugin (version was {actual}, but expected {expected}). Try running: dprint upgrade")]
83 CliTooOld { actual: u32, expected: u32 },
84 #[error("Error waiting on message ({message_id}). {error}")]
85 WaitOnMessage {
86 message_id: u32,
87 error: tokio::sync::oneshot::error::RecvError,
88 },
89 #[error("Unexpected data channel for success response: {0}")]
90 UnexpectedDataForSuccess(u32),
91 #[error("Unexpected format channel for success response: {0}")]
92 UnexpectedFormatForSuccess(u32),
93 #[error("Unexpected success channel for data response: {0}")]
94 UnexpectedSuccessForData(u32),
95 #[error("Unexpected format channel for data response: {0}")]
96 UnexpectedFormatForData(u32),
97 #[error("Unexpected success channel for format response: {0}")]
98 UnexpectedSuccessForFormat(u32),
99 #[error("Unexpected data channel for format response: {0}")]
100 UnexpectedDataForFormat(u32),
101 #[error("Unknown message kind: {0}")]
102 UnknownMessageKind(u32),
103 #[error("Could not find host format callback for message id: {0}")]
104 HostFormatCallbackNotFound(u32),
105}
106
107impl From<CommunicatorError> for FormatError {
108 fn from(err: CommunicatorError) -> Self {
109 FormatError::new(err)
110 }
111}
112
113pub type HostFormatCallback = Rc<dyn Fn(HostFormatRequest) -> LocalBoxFuture<'static, FormatResult>>;
114
115pub struct ProcessPluginCommunicatorFormatRequest {
116 pub file_path: PathBuf,
117 pub file_bytes: Vec<u8>,
118 pub range: FormatRange,
119 pub config_id: FormatConfigId,
120 pub override_config: ConfigKeyMap,
121 pub on_host_format: HostFormatCallback,
122 pub token: DprintCancellationToken,
123}
124
125enum MessageResponseChannel {
126 Acknowledgement(oneshot::Sender<Result<()>>),
127 Data(oneshot::Sender<Result<Vec<u8>>>),
128 Format(oneshot::Sender<Result<Option<Vec<u8>>>>),
129}
130
131struct Context {
132 stdin_writer: SingleThreadMessageWriter<ProcessPluginMessage>,
133 shutdown_flag: Arc<AtomicFlag>,
134 id_generator: IdGenerator,
135 messages: RcIdStore<MessageResponseChannel>,
136 format_request_tokens: RcIdStore<Arc<CancellationToken>>,
137 host_format_callbacks: RcIdStore<HostFormatCallback>,
138}
139
140pub struct ProcessPluginCommunicator {
142 child: RefCell<Option<Child>>,
143 context: Rc<Context>,
144}
145
146impl Drop for ProcessPluginCommunicator {
147 fn drop(&mut self) {
148 self.kill();
149 }
150}
151
152impl ProcessPluginCommunicator {
153 pub async fn new(executable_file_path: &Path, on_std_err: impl Fn(String) + Clone + Send + Sync + 'static) -> Result<Self> {
154 ProcessPluginCommunicator::new_internal(executable_file_path, false, on_std_err).await
155 }
156
157 pub async fn new_with_init(executable_file_path: &Path, on_std_err: impl Fn(String) + Clone + Send + Sync + 'static) -> Result<Self> {
159 ProcessPluginCommunicator::new_internal(executable_file_path, true, on_std_err).await
160 }
161
162 async fn new_internal(executable_file_path: &Path, is_init: bool, on_std_err: impl Fn(String) + Clone + Send + Sync + 'static) -> Result<Self> {
163 let mut args = vec!["--parent-pid".to_string(), std::process::id().to_string()];
164 if is_init {
165 args.push("--init".to_string());
166 }
167
168 let shutdown_flag = Arc::new(AtomicFlag::default());
169 let mut child = Command::new(executable_file_path)
170 .args(&args)
171 .stdin(Stdio::piped())
172 .stderr(Stdio::piped())
173 .stdout(Stdio::piped())
174 .spawn()
175 .map_err(|err| CommunicatorError::StartProcess {
176 executable: executable_file_path.display().to_string(),
177 args: args.join(" "),
178 error: err,
179 })?;
180
181 let stderr = child.stderr.take().unwrap();
183 crate::async_runtime::spawn_blocking({
184 let shutdown_flag = shutdown_flag.clone();
185 let on_std_err = on_std_err.clone();
186 move || {
187 std_err_redirect(shutdown_flag, stderr, on_std_err);
188 }
189 });
190
191 let mut stdout_reader = MessageReader::new(child.stdout.take().unwrap());
193 let mut stdin_writer = MessageWriter::new(child.stdin.take().unwrap());
194
195 let (mut stdout_reader, stdin_writer, schema_version) = crate::async_runtime::spawn_blocking(move || {
196 let schema_version = get_plugin_schema_version(&mut stdout_reader, &mut stdin_writer).map_err(CommunicatorError::SchemaVerification)?;
197 Ok::<_, FormatError>((stdout_reader, stdin_writer, schema_version))
198 })
199 .await??;
200
201 if schema_version != PLUGIN_SCHEMA_VERSION {
202 let _ = child.kill();
204 let err = if schema_version < PLUGIN_SCHEMA_VERSION {
205 CommunicatorError::PluginTooOld {
206 actual: schema_version,
207 expected: PLUGIN_SCHEMA_VERSION,
208 }
209 } else {
210 CommunicatorError::CliTooOld {
211 actual: schema_version,
212 expected: PLUGIN_SCHEMA_VERSION,
213 }
214 };
215 return Err(err.into());
216 }
217
218 let stdin_writer = SingleThreadMessageWriter::for_stdin(stdin_writer);
219 let context = Rc::new(Context {
220 id_generator: Default::default(),
221 shutdown_flag,
222 stdin_writer,
223 messages: Default::default(),
224 format_request_tokens: Default::default(),
225 host_format_callbacks: Default::default(),
226 });
227
228 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
230 crate::async_runtime::spawn_blocking({
231 let shutdown_flag = context.shutdown_flag.clone();
232 let on_std_err = on_std_err.clone();
233 move || {
234 loop {
235 match ProcessPluginMessage::read(&mut stdout_reader) {
236 Ok(message) => {
237 if tx.send(message).is_err() {
238 break; }
240 }
241 Err(err) if err.kind() == ErrorKind::BrokenPipe => {
242 break;
243 }
244 Err(err) => {
245 if !shutdown_flag.is_raised() {
246 on_std_err(format!("Error reading stdout message: {:#}", err));
247 }
248 break;
249 }
250 }
251 }
252 }
253 });
254 crate::async_runtime::spawn({
255 let context = context.clone();
256 async move {
257 while let Some(message) = rx.recv().await {
258 if let Err(err) = handle_stdout_message(message, &context) {
259 if !context.shutdown_flag.is_raised() {
260 on_std_err(format!("Error reading stdout message: {:#}", err));
261 }
262 break;
263 }
264 }
265 context.messages.take_all();
267 }
268 });
269
270 Ok(Self {
271 child: RefCell::new(Some(child)),
272 context,
273 })
274 }
275
276 pub async fn shutdown(&self) {
278 if self.context.shutdown_flag.raise() {
279 tokio::select! {
281 _ = self.send_with_acknowledgement(MessageBody::Close) => {}
285 _ = tokio::time::sleep(Duration::from_millis(250)) => {
286 self.kill();
287 }
288 }
289 } else {
290 self.kill();
291 }
292 }
293
294 pub fn kill(&self) {
295 self.context.shutdown_flag.raise();
296 if let Some(mut child) = self.child.borrow_mut().take() {
297 let _ignore = child.kill();
298 }
299 }
300
301 pub async fn register_config(&self, config_id: FormatConfigId, global_config: &GlobalConfiguration, plugin_config: &ConfigKeyMap) -> Result<()> {
302 let global_config = serde_json::to_vec(global_config)?;
303 let plugin_config = serde_json::to_vec(plugin_config)?;
304 self
305 .send_with_acknowledgement(MessageBody::RegisterConfig(RegisterConfigMessageBody {
306 config_id,
307 global_config,
308 plugin_config,
309 }))
310 .await?;
311 Ok(())
312 }
313
314 pub async fn release_config(&self, config_id: FormatConfigId) -> Result<()> {
315 self.send_with_acknowledgement(MessageBody::ReleaseConfig(config_id)).await?;
316 Ok(())
317 }
318
319 pub async fn ask_is_alive(&self) -> bool {
320 self.send_with_acknowledgement(MessageBody::IsAlive).await.is_ok()
321 }
322
323 pub async fn plugin_info(&self) -> Result<PluginInfo> {
324 self.send_receiving_data(MessageBody::GetPluginInfo).await
325 }
326
327 pub async fn license_text(&self) -> Result<String> {
328 self.send_receiving_string(MessageBody::GetLicenseText).await
329 }
330
331 pub async fn resolved_config(&self, config_id: FormatConfigId) -> Result<String> {
332 self.send_receiving_string(MessageBody::GetResolvedConfig(config_id)).await
333 }
334
335 pub async fn file_matching_info(&self, config_id: FormatConfigId) -> Result<FileMatchingInfo> {
336 let data = self.send_receiving_bytes(MessageBody::GetFileMatchingInfo(config_id)).await??;
337 serde_json::from_slice(&data).map_err(|err| FormatError::new(format!("{FILE_MATCHING_INFO_ERROR_MESSAGE}\n\n{err:#}")))
338 }
339
340 pub async fn config_diagnostics(&self, config_id: FormatConfigId) -> Result<Vec<ConfigurationDiagnostic>> {
341 self.send_receiving_data(MessageBody::GetConfigDiagnostics(config_id)).await
342 }
343
344 pub async fn check_config_updates(&self, message: &CheckConfigUpdatesMessageBody) -> Result<Vec<ConfigChange>> {
345 let bytes = serde_json::to_vec(&message)?;
346 let response: CheckConfigUpdatesResponseBody = self.send_receiving_data(MessageBody::CheckConfigUpdates(bytes)).await?;
347 Ok(response.changes)
348 }
349
350 pub async fn format_text(&self, request: ProcessPluginCommunicatorFormatRequest) -> FormatResult {
351 let (tx, rx) = oneshot::channel::<Result<Option<Vec<u8>>>>();
352
353 let message_id = self.context.id_generator.next();
354 let store_guard = self.context.host_format_callbacks.store_with_guard(message_id, request.on_host_format);
355 let maybe_result = self
356 .send_message_with_id(
357 message_id,
358 MessageBody::Format(FormatMessageBody {
359 file_path: request.file_path,
360 file_bytes: request.file_bytes,
361 range: request.range,
362 config_id: request.config_id,
363 override_config: serde_json::to_vec(&request.override_config).unwrap(),
364 }),
365 MessageResponseChannel::Format(tx),
366 rx,
367 request.token.clone(),
368 )
369 .await;
370
371 drop(store_guard); if request.token.is_cancelled() {
374 Ok(None)
375 } else {
376 match maybe_result {
377 Ok(result) => result,
378 Err(err) => Err(CriticalFormatError(err).into()),
379 }
380 }
381 }
382
383 pub async fn is_process_alive(&self) -> bool {
385 if self.context.shutdown_flag.is_raised() {
386 false
387 } else {
388 self.ask_is_alive().await
389 }
390 }
391
392 async fn send_with_acknowledgement(&self, body: MessageBody) -> Result<()> {
393 let (tx, rx) = oneshot::channel::<Result<()>>();
394 self
395 .send_message(body, MessageResponseChannel::Acknowledgement(tx), rx, Arc::new(NullCancellationToken))
396 .await?
397 }
398
399 async fn send_receiving_string(&self, body: MessageBody) -> Result<String> {
400 let data = self.send_receiving_bytes(body).await??;
401 Ok(String::from_utf8(data)?)
402 }
403
404 async fn send_receiving_data<T: DeserializeOwned>(&self, body: MessageBody) -> Result<T> {
405 let data = self.send_receiving_bytes(body).await??;
406 Ok(serde_json::from_slice(&data)?)
407 }
408
409 async fn send_receiving_bytes(&self, body: MessageBody) -> Result<Result<Vec<u8>>> {
410 let (tx, rx) = oneshot::channel::<Result<Vec<u8>>>();
411 self
412 .send_message(body, MessageResponseChannel::Data(tx), rx, Arc::new(NullCancellationToken))
413 .await
414 }
415
416 async fn send_message<T: Default>(
417 &self,
418 body: MessageBody,
419 response_channel: MessageResponseChannel,
420 receiver: oneshot::Receiver<Result<T>>,
421 token: Arc<dyn super::super::CancellationToken>,
422 ) -> Result<Result<T>> {
423 let message_id = self.context.id_generator.next();
424 self.send_message_with_id(message_id, body, response_channel, receiver, token).await
425 }
426
427 async fn send_message_with_id<T: Default>(
428 &self,
429 message_id: u32,
430 body: MessageBody,
431 response_channel: MessageResponseChannel,
432 receiver: oneshot::Receiver<Result<T>>,
433 token: Arc<dyn super::super::CancellationToken>,
434 ) -> Result<Result<T>> {
435 let mut drop_guard = DropGuardAction::new(|| {
436 self.context.messages.take(message_id);
438 let _ = self.context.stdin_writer.send(ProcessPluginMessage {
440 id: self.context.id_generator.next(),
441 body: MessageBody::CancelFormat(message_id),
442 });
443 });
444
445 self.context.messages.store(message_id, response_channel);
446 self.context.stdin_writer.send(ProcessPluginMessage { id: message_id, body })?;
447 tokio::select! {
448 _ = token.wait_cancellation() => {
449 drop(drop_guard); Ok(Ok(Default::default()))
451 }
452 response = receiver => {
453 drop_guard.forget(); match response {
455 Ok(data) => Ok(data),
456 Err(err) => Err(CommunicatorError::WaitOnMessage { message_id, error: err }.into()),
457 }
458 }
459 }
460 }
461}
462
463fn get_plugin_schema_version<TRead: Read + Unpin, TWrite: Write + Unpin>(
464 reader: &mut MessageReader<TRead>,
465 writer: &mut MessageWriter<TWrite>,
466) -> std::result::Result<u32, SchemaVersionError> {
467 writer.send_u32(0).map_err(SchemaVersionError::Ask)?; writer.flush().map_err(SchemaVersionError::Flush)?;
470 let acknowledgement_response = reader.read_u32().map_err(SchemaVersionError::ReadAcknowledgement)?;
471 if acknowledgement_response != 0 {
472 return Err(SchemaVersionError::UnexpectedAcknowledgement(acknowledgement_response));
473 }
474 reader.read_u32().map_err(SchemaVersionError::ReadVersion)
475}
476
477fn std_err_redirect(shutdown_flag: Arc<AtomicFlag>, stderr: ChildStderr, on_std_err: impl Fn(String) + Send + Sync + 'static) {
478 let reader = std::io::BufReader::new(stderr);
479 for line in reader.lines() {
480 match line {
481 Ok(line) => on_std_err(line),
482 Err(err) => {
483 if shutdown_flag.is_raised() || err.kind() == ErrorKind::BrokenPipe {
484 return;
485 } else {
486 on_std_err(format!("Error reading line from process plugin stderr. {:#}", err));
487 }
488 }
489 }
490 }
491}
492
493fn handle_stdout_message(message: ProcessPluginMessage, context: &Rc<Context>) -> Result<()> {
494 match message.body {
495 MessageBody::Success(message_id) => match context.messages.take(message_id) {
496 Some(MessageResponseChannel::Acknowledgement(channel)) => {
497 let _ignore = channel.send(Ok(()));
498 }
499 Some(MessageResponseChannel::Data(channel)) => {
500 let _ignore = channel.send(Err(CommunicatorError::UnexpectedDataForSuccess(message_id).into()));
501 }
502 Some(MessageResponseChannel::Format(channel)) => {
503 let _ignore = channel.send(Err(CommunicatorError::UnexpectedFormatForSuccess(message_id).into()));
504 }
505 None => {}
506 },
507 MessageBody::DataResponse(response) => match context.messages.take(response.message_id) {
508 Some(MessageResponseChannel::Acknowledgement(channel)) => {
509 let _ignore = channel.send(Err(CommunicatorError::UnexpectedSuccessForData(response.message_id).into()));
510 }
511 Some(MessageResponseChannel::Data(channel)) => {
512 let _ignore = channel.send(Ok(response.data));
513 }
514 Some(MessageResponseChannel::Format(channel)) => {
515 let _ignore = channel.send(Err(CommunicatorError::UnexpectedFormatForData(response.message_id).into()));
516 }
517 None => {}
518 },
519 MessageBody::Error(response) => {
520 let err: FormatError = String::from_utf8_lossy(&response.data).into_owned().into();
521 match context.messages.take(response.message_id) {
522 Some(MessageResponseChannel::Acknowledgement(channel)) => {
523 let _ignore = channel.send(Err(err));
524 }
525 Some(MessageResponseChannel::Data(channel)) => {
526 let _ignore = channel.send(Err(err));
527 }
528 Some(MessageResponseChannel::Format(channel)) => {
529 let _ignore = channel.send(Err(err));
530 }
531 None => {}
532 }
533 }
534 MessageBody::FormatResponse(response) => match context.messages.take(response.message_id) {
535 Some(MessageResponseChannel::Acknowledgement(channel)) => {
536 let _ignore = channel.send(Err(CommunicatorError::UnexpectedSuccessForFormat(response.message_id).into()));
537 }
538 Some(MessageResponseChannel::Data(channel)) => {
539 let _ignore = channel.send(Err(CommunicatorError::UnexpectedDataForFormat(response.message_id).into()));
540 }
541 Some(MessageResponseChannel::Format(channel)) => {
542 let _ignore = channel.send(Ok(response.data));
543 }
544 None => {}
545 },
546 MessageBody::CancelFormat(message_id) => {
547 if let Some(token) = context.format_request_tokens.take(message_id) {
548 token.cancel();
549 }
550 context.host_format_callbacks.take(message_id);
551 }
553 MessageBody::HostFormat(body) => {
554 let context = context.clone();
557 crate::async_runtime::spawn(async move {
558 let result = host_format(context.clone(), message.id, body).await;
559
560 let _ignore = context.stdin_writer.send(ProcessPluginMessage {
563 id: context.id_generator.next(),
564 body: match result {
565 Ok(result) => MessageBody::FormatResponse(ResponseBody {
566 message_id: message.id,
567 data: result,
568 }),
569 Err(err) => MessageBody::Error(ResponseBody {
570 message_id: message.id,
571 data: error_to_string(&err).into_bytes(),
572 }),
573 },
574 });
575 });
576 }
577 MessageBody::IsAlive => {
578 let _ = context.stdin_writer.send(ProcessPluginMessage {
580 id: context.id_generator.next(),
581 body: MessageBody::Success(message.id),
582 });
583 }
584 MessageBody::Format(_)
585 | MessageBody::Close
586 | MessageBody::GetPluginInfo
587 | MessageBody::GetLicenseText
588 | MessageBody::RegisterConfig(_)
589 | MessageBody::ReleaseConfig(_)
590 | MessageBody::GetConfigDiagnostics(_)
591 | MessageBody::GetFileMatchingInfo(_)
592 | MessageBody::GetResolvedConfig(_)
593 | MessageBody::CheckConfigUpdates(_) => {
594 let _ = context.stdin_writer.send(ProcessPluginMessage {
595 id: context.id_generator.next(),
596 body: MessageBody::Error(ResponseBody {
597 message_id: message.id,
598 data: "Unsupported plugin to CLI message.".as_bytes().to_vec(),
599 }),
600 });
601 }
602 MessageBody::Unknown(message_kind) => {
605 return Err(CommunicatorError::UnknownMessageKind(message_kind).into());
606 }
607 }
608
609 Ok(())
610}
611
612async fn host_format(context: Rc<Context>, message_id: u32, body: HostFormatMessageBody) -> FormatResult {
613 let Some(callback) = context.host_format_callbacks.get_cloned(body.original_message_id) else {
614 return FormatResult::Err(CommunicatorError::HostFormatCallbackNotFound(body.original_message_id).into());
615 };
616
617 let token = Arc::new(CancellationToken::new());
618 let store_guard = context.format_request_tokens.store_with_guard(message_id, token.clone());
619 let result = callback(HostFormatRequest {
620 file_path: body.file_path,
621 file_bytes: body.file_text,
622 range: body.range,
623 override_config: serde_json::from_slice(&body.override_config).unwrap(),
624 token,
625 })
626 .await;
627 drop(store_guard); result
629}