Properly support prettier plugins
This commit is contained in:
parent
afee29ad3f
commit
8a807102a6
8 changed files with 100 additions and 33 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue