Properly support prettier plugins

This commit is contained in:
Kirill Bulatov 2023-09-22 23:49:03 +03:00
parent afee29ad3f
commit 8a807102a6
8 changed files with 100 additions and 33 deletions

1
Cargo.lock generated
View file

@ -5528,6 +5528,7 @@ dependencies = [
"futures 0.3.28", "futures 0.3.28",
"gpui", "gpui",
"language", "language",
"log",
"lsp", "lsp",
"node_runtime", "node_runtime",
"serde", "serde",

View file

@ -338,10 +338,6 @@ pub trait LspAdapter: 'static + Send + Sync {
Default::default() Default::default()
} }
// TODO kb enable this for
// markdown somehow?
// tailwind (needs a css plugin, there are 2 of them)
// svelte (needs a plugin)
fn enabled_formatters(&self) -> Vec<BundledFormatter> { fn enabled_formatters(&self) -> Vec<BundledFormatter> {
Vec::new() Vec::new()
} }
@ -350,15 +346,15 @@ pub trait LspAdapter: 'static + Send + Sync {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum BundledFormatter { pub enum BundledFormatter {
Prettier { Prettier {
parser_name: &'static str, parser_name: Option<&'static str>,
plugin_names: Vec<String>, plugin_names: Vec<&'static str>,
}, },
} }
impl BundledFormatter { impl BundledFormatter {
pub fn prettier(parser_name: &'static str) -> Self { pub fn prettier(parser_name: &'static str) -> Self {
Self::Prettier { Self::Prettier {
parser_name, parser_name: Some(parser_name),
plugin_names: Vec::new(), plugin_names: Vec::new(),
} }
} }

View file

@ -15,6 +15,7 @@ lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime"} node_runtime = { path = "../node_runtime"}
util = { path = "../util" } util = { path = "../util" }
log.workspace = true
serde.workspace = true serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
serde_json.workspace = true serde_json.workspace = true

View file

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
use collections::HashMap; use collections::{HashMap, HashSet};
use fs::Fs; use fs::Fs;
use gpui::{AsyncAppContext, ModelHandle}; use gpui::{AsyncAppContext, ModelHandle};
use language::language_settings::language_settings; use language::language_settings::language_settings;
@ -29,6 +29,7 @@ pub struct LocateStart {
pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js";
pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js");
const PRETTIER_PACKAGE_NAME: &str = "prettier"; const PRETTIER_PACKAGE_NAME: &str = "prettier";
const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss";
impl Prettier { impl Prettier {
// This was taken from the prettier-vscode extension. // This was taken from the prettier-vscode extension.
@ -206,17 +207,64 @@ impl Prettier {
let path = buffer_file let path = buffer_file
.map(|file| file.full_path(cx)) .map(|file| file.full_path(cx))
.map(|path| path.to_path_buf()); .map(|path| path.to_path_buf());
let parser = buffer_language.and_then(|language| { let parsers_with_plugins = buffer_language
language .into_iter()
.lsp_adapters() .flat_map(|language| {
.iter() language
.flat_map(|adapter| adapter.enabled_formatters()) .lsp_adapters()
.find_map(|formatter| match formatter { .iter()
BundledFormatter::Prettier { parser_name, .. } => { .flat_map(|adapter| adapter.enabled_formatters())
Some(parser_name.to_string()) .filter_map(|formatter| match formatter {
BundledFormatter::Prettier {
parser_name,
plugin_names,
} => Some((parser_name, plugin_names)),
})
})
.fold(
HashMap::default(),
|mut parsers_with_plugins, (parser_name, plugins)| {
match parser_name {
Some(parser_name) => parsers_with_plugins
.entry(parser_name)
.or_insert_with(HashSet::default)
.extend(plugins),
None => parsers_with_plugins.values_mut().for_each(|existing_plugins| {
existing_plugins.extend(plugins.iter());
}),
} }
}) parsers_with_plugins
}); },
);
let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len());
if parsers_with_plugins.len() > 1 {
log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}");
}
// TODO kb move the entire prettier server js file into *.mjs one instead?
let plugin_name_into_path = |plugin_name: &str| self.prettier_dir.join("node_modules").join(plugin_name).join("dist").join("index.mjs");
let (parser, plugins) = match selected_parser_with_plugins {
Some((parser, plugins)) => {
// Tailwind plugin requires being added last
// https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
let mut add_tailwind_back = false;
let mut plugins = plugins.into_iter().filter(|&&plugin_name| {
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
add_tailwind_back = true;
false
} else {
true
}
}).map(|plugin_name| plugin_name_into_path(plugin_name)).collect::<Vec<_>>();
if add_tailwind_back {
plugins.push(plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME));
}
(Some(parser.to_string()), plugins)
},
None => (None, Vec::new()),
};
let prettier_options = if self.default { let prettier_options = if self.default {
let language_settings = language_settings(buffer_language, buffer_file, cx); let language_settings = language_settings(buffer_language, buffer_file, cx);
@ -246,6 +294,7 @@ impl Prettier {
text: buffer.text(), text: buffer.text(),
options: FormatOptions { options: FormatOptions {
parser, parser,
plugins,
// TODO kb is not absolute now // TODO kb is not absolute now
path, path,
prettier_options, prettier_options,
@ -345,6 +394,7 @@ struct FormatParams {
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct FormatOptions { struct FormatOptions {
plugins: Vec<PathBuf>,
parser: Option<String>, parser: Option<String>,
#[serde(rename = "filepath")] #[serde(rename = "filepath")]
path: Option<PathBuf>, path: Option<PathBuf>,

View file

@ -153,6 +153,7 @@ async function handleMessage(message, prettier) {
const options = { const options = {
...(params.options.prettierOptions || prettier.config), ...(params.options.prettierOptions || prettier.config),
parser: params.options.parser, parser: params.options.parser,
plugins: params.options.plugins,
path: params.options.path path: params.options.path
}; };
// TODO kb always resolve prettier config for each file. // TODO kb always resolve prettier config for each file.

View file

@ -852,6 +852,7 @@ impl Project {
} }
} }
let worktree = buffer_file let worktree = buffer_file
// TODO kb wrong usage (+ look around for another one like this)
.map(|f| f.worktree_id()) .map(|f| f.worktree_id())
.map(WorktreeId::from_usize); .map(WorktreeId::from_usize);
language_formatters_to_check.push(( language_formatters_to_check.push((
@ -912,7 +913,7 @@ impl Project {
} }
for (worktree, language, settings) in language_formatters_to_check { for (worktree, language, settings) in language_formatters_to_check {
self.maybe_start_default_formatters(worktree, &language, &settings, cx); self.install_default_formatters(worktree, &language, &settings, cx);
} }
// Start all the newly-enabled language servers. // Start all the newly-enabled language servers.
@ -2669,7 +2670,7 @@ impl Project {
.map(|f| f.worktree_id()) .map(|f| f.worktree_id())
.map(WorktreeId::from_usize); .map(WorktreeId::from_usize);
let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); self.install_default_formatters(worktree, &new_language, &settings, cx);
if let Some(file) = File::from_dyn(buffer_file.as_ref()) { if let Some(file) = File::from_dyn(buffer_file.as_ref()) {
let worktree = file.worktree.clone(); let worktree = file.worktree.clone();
@ -6395,7 +6396,7 @@ impl Project {
.prettier_instances .prettier_instances
.iter() .iter()
.filter_map(|((worktree_id, prettier_path), prettier_task)| { .filter_map(|((worktree_id, prettier_path), prettier_task)| {
if worktree_id == &Some(current_worktree_id) { if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) {
Some((*worktree_id, prettier_path.clone(), prettier_task.clone())) Some((*worktree_id, prettier_path.clone(), prettier_task.clone()))
} else { } else {
None None
@ -6412,7 +6413,7 @@ impl Project {
.await .await
.with_context(|| { .with_context(|| {
format!( format!(
"clearing prettier {prettier_path:?} cache for worktree {worktree_id:?}" "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update"
) )
}) })
.map_err(Arc::new) .map_err(Arc::new)
@ -8410,6 +8411,7 @@ impl Project {
.supplementary_language_servers .supplementary_language_servers
.insert(new_server_id, (name, Arc::clone(prettier.server()))); .insert(new_server_id, (name, Arc::clone(prettier.server())));
// TODO kb could there be a race with multiple default prettier instances added? // 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)); cx.emit(Event::LanguageServerAdded(new_server_id));
}); });
} }
@ -8426,7 +8428,7 @@ impl Project {
Some(task) Some(task)
} }
fn maybe_start_default_formatters( fn install_default_formatters(
&self, &self,
worktree: Option<WorktreeId>, worktree: Option<WorktreeId>,
new_language: &Language, new_language: &Language,
@ -8458,14 +8460,10 @@ impl Project {
}; };
let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path();
if let Some(_already_running) = self let already_running_prettier = self
.prettier_instances .prettier_instances
.get(&(worktree, default_prettier_dir.to_path_buf())) .get(&(worktree, default_prettier_dir.to_path_buf()))
{ .cloned();
// TODO kb need to compare plugins, install missing and restart prettier
// TODO kb move the entire prettier init logic into prettier.rs
return;
}
let fs = Arc::clone(&self.fs); let fs = Arc::clone(&self.fs);
cx.background() cx.background()
@ -8478,8 +8476,7 @@ impl Project {
let packages_to_versions = future::try_join_all( let packages_to_versions = future::try_join_all(
prettier_plugins prettier_plugins
.iter() .iter()
.map(|s| s.as_str()) .chain(Some(&"prettier"))
.chain(Some("prettier"))
.map(|package_name| async { .map(|package_name| async {
let returned_package_name = package_name.to_string(); let returned_package_name = package_name.to_string();
let latest_version = node.npm_package_latest_version(package_name) let latest_version = node.npm_package_latest_version(package_name)
@ -8497,6 +8494,13 @@ impl Project {
(package.as_str(), version.as_str()) (package.as_str(), version.as_str())
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?;
if !prettier_plugins.is_empty() {
if let Some(prettier) = already_running_prettier {
prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?;
}
}
anyhow::Ok(()) anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);

View file

@ -1,7 +1,7 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use futures::StreamExt; use futures::StreamExt;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary; use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use serde_json::json; use serde_json::json;
@ -95,6 +95,13 @@ impl LspAdapter for SvelteLspAdapter {
"provideFormatter": true "provideFormatter": true
})) }))
} }
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
vec![BundledFormatter::Prettier {
parser_name: Some("svelte"),
plugin_names: vec!["prettier-plugin-svelte"],
}]
}
} }
async fn get_cached_server_binary( async fn get_cached_server_binary(

View file

@ -6,7 +6,7 @@ use futures::{
FutureExt, StreamExt, FutureExt, StreamExt,
}; };
use gpui::AppContext; use gpui::AppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use language::{BundledFormatter, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary; use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use serde_json::{json, Value}; use serde_json::{json, Value};
@ -127,6 +127,13 @@ impl LspAdapter for TailwindLspAdapter {
.into_iter(), .into_iter(),
) )
} }
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
vec![BundledFormatter::Prettier {
parser_name: None,
plugin_names: vec!["prettier-plugin-tailwindcss"],
}]
}
} }
async fn get_cached_server_binary( async fn get_cached_server_binary(