Autodetect parser name with prettier by default (#11558)

Closes https://github.com/zed-industries/zed/issues/11517 

* Removes forced prettier parser name for languages, making `auto`
command to run prettier on every file by default.
* Moves prettier configs away from plugin language declarations into
language settings

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2024-05-15 22:51:46 +03:00 committed by GitHub
parent 52c70c1082
commit cb430fc3e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 291 additions and 209 deletions

View File

@ -103,14 +103,7 @@
// Hide the values of in variables from visual display in private files // Hide the values of in variables from visual display in private files
"redact_private_values": false, "redact_private_values": false,
// Globs to match against file paths to determine if a file is private. // Globs to match against file paths to determine if a file is private.
"private_files": [ "private_files": ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"],
"**/.env*",
"**/*.pem",
"**/*.key",
"**/*.cert",
"**/*.crt",
"**/secrets.yml"
],
// Whether to use additional LSP queries to format (and amend) the code after // Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities. // every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true, "use_on_type_format": true,
@ -311,9 +304,7 @@
// The list of language servers to use (or disable) for all languages. // The list of language servers to use (or disable) for all languages.
// //
// This is typically customized on a per-language basis. // This is typically customized on a per-language basis.
"language_servers": [ "language_servers": ["..."],
"..."
],
// When to automatically save edited buffers. This setting can // When to automatically save edited buffers. This setting can
// take four values. // take four values.
// //
@ -448,9 +439,7 @@
"copilot": { "copilot": {
// The set of glob patterns for which copilot should be disabled // The set of glob patterns for which copilot should be disabled
// in any matching file. // in any matching file.
"disabled_globs": [ "disabled_globs": [".env"]
".env"
]
}, },
// Settings specific to journaling // Settings specific to journaling
"journal": { "journal": {
@ -561,12 +550,7 @@
// Default directories to search for virtual environments, relative // Default directories to search for virtual environments, relative
// to the current working directory. We recommend overriding this // to the current working directory. We recommend overriding this
// in your project's settings, rather than globally. // in your project's settings, rather than globally.
"directories": [ "directories": [".env", "env", ".venv", "venv"],
".env",
"env",
".venv",
"venv"
],
// Can also be 'csh', 'fish', and `nushell` // Can also be 'csh', 'fish', and `nushell`
"activate_script": "default" "activate_script": "default"
} }
@ -609,19 +593,30 @@
}, },
// Different settings for specific languages. // Different settings for specific languages.
"languages": { "languages": {
"C++": { "Astro": {
"format_on_save": "off" "prettier": {
"allowed": true,
"plugins": ["prettier-plugin-astro"]
}
},
"Blade": {
"prettier": {
"allowed": true
}
}, },
"C": { "C": {
"format_on_save": "off" "format_on_save": "off"
}, },
"C++": {
"format_on_save": "off"
},
"CSS": {
"prettier": {
"allowed": true
}
},
"Elixir": { "Elixir": {
"language_servers": [ "language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
"elixir-ls",
"!next-ls",
"!lexical",
"..."
]
}, },
"Gleam": { "Gleam": {
"tab_size": 2 "tab_size": 2
@ -631,32 +626,120 @@
"source.organizeImports": true "source.organizeImports": true
} }
}, },
"GraphQL": {
"prettier": {
"allowed": true
}
},
"HEEX": { "HEEX": {
"language_servers": [ "language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
"elixir-ls", },
"!next-ls", "HTML": {
"!lexical", "prettier": {
"..." "allowed": true
] }
},
"Java": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-java"]
}
},
"JavaScript": {
"prettier": {
"allowed": true
}
},
"JSON": {
"prettier": {
"allowed": true
}
}, },
"Make": { "Make": {
"hard_tabs": true "hard_tabs": true
}, },
"Markdown": { "Markdown": {
"format_on_save": "off" "format_on_save": "off",
"prettier": {
"allowed": true
}
},
"PHP": {
"prettier": {
"allowed": true,
"plugins": ["@prettier/plugin-php"]
}
}, },
"Prisma": { "Prisma": {
"tab_size": 2 "tab_size": 2
}, },
"Ruby": { "Ruby": {
"language_servers": ["solargraph", "!ruby-lsp", "..."] "language_servers": ["solargraph", "!ruby-lsp", "..."]
},
"SCSS": {
"prettier": {
"allowed": true
}
},
"SQL": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-sql"]
}
},
"Svelte": {
"prettier": {
"allowed": true,
"plugins": ["prettier-plugin-svelte"]
}
},
"TSX": {
"prettier": {
"allowed": true
}
},
"Twig": {
"prettier": {
"allowed": true
}
},
"TypeScript": {
"prettier": {
"allowed": true
}
},
"Vue.js": {
"prettier": {
"allowed": true
}
},
"XML": {
"prettier": {
"allowed": true,
"plugins": ["@prettier/plugin-xml"]
}
},
"YAML": {
"prettier": {
"allowed": true
}
} }
}, },
// Zed's Prettier integration settings. // Zed's Prettier integration settings.
// If Prettier is enabled, Zed will use this for its Prettier instance for any applicable file, if // Allows to enable/disable formatting with Prettier
// project has no other Prettier installed. // and configure default Prettier, used when no project-level Prettier installation is found.
"prettier": { "prettier": {
// Use regular Prettier json configuration: // // Whether to consider prettier formatter or not when attempting to format a file.
// "allowed": false,
//
// // Use regular Prettier json configuration.
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
// // the project has no other Prettier installed.
// "plugins": [],
//
// // Use regular Prettier json configuration.
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
// // the project has no other Prettier installed.
// "trailingComma": "es5", // "trailingComma": "es5",
// "tabWidth": 4, // "tabWidth": 4,
// "semi": false, // "semi": false,

View File

@ -17,7 +17,7 @@ use gpui::{
MouseDownEvent, TestAppContext, MouseDownEvent, TestAppContext,
}; };
use language::{ use language::{
language_settings::{AllLanguageSettings, Formatter}, language_settings::{AllLanguageSettings, Formatter, PrettierSettings},
tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
}; };
@ -4445,18 +4445,17 @@ async fn test_prettier_formatting_buffer(
client_a.language_registry().add(Arc::new(Language::new( client_a.language_registry().add(Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: "Rust".into(), name: "TypeScript".into(),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["ts".to_string()],
..Default::default() ..Default::default()
}, },
prettier_parser_name: Some("test_parser".to_string()),
..Default::default() ..Default::default()
}, },
Some(tree_sitter_rust::language()), Some(tree_sitter_rust::language()),
))); )));
let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter( let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter(
"Rust", "TypeScript",
FakeLspAdapter { FakeLspAdapter {
prettier_plugins: vec![test_plugin], prettier_plugins: vec![test_plugin],
..Default::default() ..Default::default()
@ -4470,11 +4469,11 @@ async fn test_prettier_formatting_buffer(
let buffer_text = "let one = \"two\""; let buffer_text = "let one = \"two\"";
client_a client_a
.fs() .fs()
.insert_tree(&directory, json!({ "a.rs": buffer_text })) .insert_tree(&directory, json!({ "a.ts": buffer_text }))
.await; .await;
let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await; let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX; let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap(); let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
let project_id = active_call_a let project_id = active_call_a
@ -4482,13 +4481,17 @@ async fn test_prettier_formatting_buffer(
.await .await
.unwrap(); .unwrap();
let project_b = client_b.build_dev_server_project(project_id, cx_b).await; let project_b = client_b.build_dev_server_project(project_id, cx_b).await;
let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)); let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
cx_a.update(|cx| { cx_a.update(|cx| {
cx.update_global(|store: &mut SettingsStore, cx| { cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| { store.update_user_settings::<AllLanguageSettings>(cx, |file| {
file.defaults.formatter = Some(Formatter::Auto); file.defaults.formatter = Some(Formatter::Auto);
file.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
});
}); });
}); });
}); });
@ -4496,6 +4499,10 @@ async fn test_prettier_formatting_buffer(
cx.update_global(|store: &mut SettingsStore, cx| { cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| { store.update_user_settings::<AllLanguageSettings>(cx, |file| {
file.defaults.formatter = Some(Formatter::LanguageServer); file.defaults.formatter = Some(Formatter::LanguageServer);
file.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
});
}); });
}); });
}); });

View File

@ -12,7 +12,9 @@ use futures::StreamExt;
use gpui::{div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions}; use gpui::{div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions};
use indoc::indoc; use indoc::indoc;
use language::{ use language::{
language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, language_settings::{
AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
},
BracketPairConfig, BracketPairConfig,
Capability::ReadWrite, Capability::ReadWrite,
FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override, Point, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override, Point,
@ -6254,13 +6256,18 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["rs".to_string()],
..Default::default() ..Default::default()
}, },
// Enable Prettier formatting for the same buffer, and ensure ..LanguageConfig::default()
// LSP is called instead of Prettier.
prettier_parser_name: Some("test_parser".to_string()),
..Default::default()
}, },
Some(tree_sitter_rust::language()), Some(tree_sitter_rust::language()),
))); )));
update_test_language_settings(cx, |settings| {
// Enable Prettier formatting for the same buffer, and ensure
// LSP is called instead of Prettier.
settings.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
});
});
let mut fake_servers = language_registry.register_fake_lsp_adapter( let mut fake_servers = language_registry.register_fake_lsp_adapter(
"Rust", "Rust",
FakeLspAdapter { FakeLspAdapter {
@ -8599,27 +8606,32 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
}); });
let fs = FakeFs::new(cx.executor()); let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await; fs.insert_file("/file.ts", Default::default()).await;
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new( language_registry.add(Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: "Rust".into(), name: "TypeScript".into(),
matcher: LanguageMatcher { matcher: LanguageMatcher {
path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["ts".to_string()],
..Default::default() ..Default::default()
}, },
prettier_parser_name: Some("test_parser".to_string()),
..Default::default() ..Default::default()
}, },
Some(tree_sitter_rust::language()), Some(tree_sitter_rust::language()),
))); )));
update_test_language_settings(cx, |settings| {
settings.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
});
});
let test_plugin = "test_plugin"; let test_plugin = "test_plugin";
let _ = language_registry.register_fake_lsp_adapter( let _ = language_registry.register_fake_lsp_adapter(
"Rust", "TypeScript",
FakeLspAdapter { FakeLspAdapter {
prettier_plugins: vec![test_plugin], prettier_plugins: vec![test_plugin],
..Default::default() ..Default::default()
@ -8628,7 +8640,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX; let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
let buffer = project let buffer = project
.update(cx, |project, cx| project.open_local_buffer("/file.rs", cx)) .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
.await .await
.unwrap(); .unwrap();

View File

@ -479,6 +479,7 @@ pub struct Chunk<'a> {
} }
/// A set of edits to a given version of a buffer, computed asynchronously. /// A set of edits to a given version of a buffer, computed asynchronously.
#[derive(Debug)]
pub struct Diff { pub struct Diff {
pub(crate) base_version: clock::Global, pub(crate) base_version: clock::Global,
line_ending: LineEnding, line_ending: LineEnding,

View File

@ -602,13 +602,6 @@ pub struct LanguageConfig {
/// or a whole-word search in buffer search. /// or a whole-word search in buffer search.
#[serde(default)] #[serde(default)]
pub word_characters: HashSet<char>, pub word_characters: HashSet<char>,
/// The name of a Prettier parser that should be used for this language.
#[serde(default)]
pub prettier_parser_name: Option<String>,
/// The names of any Prettier plugins that should be used for this language.
#[serde(default)]
pub prettier_plugins: Vec<Arc<str>>,
/// Whether to indent lines using tab characters, as opposed to multiple /// Whether to indent lines using tab characters, as opposed to multiple
/// spaces. /// spaces.
#[serde(default)] #[serde(default)]
@ -700,8 +693,6 @@ impl Default for LanguageConfig {
scope_opt_in_language_servers: Default::default(), scope_opt_in_language_servers: Default::default(),
overrides: Default::default(), overrides: Default::default(),
word_characters: Default::default(), word_characters: Default::default(),
prettier_parser_name: None,
prettier_plugins: Default::default(),
collapsed_placeholder: Default::default(), collapsed_placeholder: Default::default(),
hard_tabs: Default::default(), hard_tabs: Default::default(),
tab_size: Default::default(), tab_size: Default::default(),
@ -1375,14 +1366,6 @@ impl Language {
} }
} }
pub fn prettier_parser_name(&self) -> Option<&str> {
self.config.prettier_parser_name.as_deref()
}
pub fn prettier_plugins(&self) -> &Vec<Arc<str>> {
&self.config.prettier_plugins
}
pub fn lsp_id(&self) -> String { pub fn lsp_id(&self) -> String {
match self.config.name.as_ref() { match self.config.name.as_ref() {
"Plain Text" => "plaintext".to_string(), "Plain Text" => "plaintext".to_string(),

View File

@ -715,15 +715,6 @@ impl LanguageRegistry {
.unwrap_or_default() .unwrap_or_default()
} }
pub fn all_prettier_plugins(&self) -> Vec<Arc<str>> {
let state = self.state.read();
state
.languages
.iter()
.flat_map(|language| language.config.prettier_plugins.iter().cloned())
.collect()
}
pub fn update_lsp_status( pub fn update_lsp_status(
&self, &self,
server_name: LanguageServerName, server_name: LanguageServerName,

View File

@ -89,9 +89,7 @@ pub struct LanguageSettings {
/// How to perform a buffer format. /// How to perform a buffer format.
pub formatter: Formatter, pub formatter: Formatter,
/// Zed's Prettier integration settings. /// Zed's Prettier integration settings.
/// If Prettier is enabled, Zed will use this for its Prettier instance for any applicable file, if pub prettier: PrettierSettings,
/// the project has no other Prettier installed.
pub prettier: HashMap<String, serde_json::Value>,
/// Whether to use language servers to provide code intelligence. /// Whether to use language servers to provide code intelligence.
pub enable_language_server: bool, pub enable_language_server: bool,
/// The list of language servers to use (or disable) for this language. /// The list of language servers to use (or disable) for this language.
@ -267,12 +265,12 @@ pub struct LanguageSettingsContent {
#[serde(default)] #[serde(default)]
pub formatter: Option<Formatter>, pub formatter: Option<Formatter>,
/// Zed's Prettier integration settings. /// Zed's Prettier integration settings.
/// If Prettier is enabled, Zed will use this for its Prettier instance for any applicable file, if /// Allows to enable/disable formatting with Prettier
/// the project has no other Prettier installed. /// and configure default Prettier, used when no project-level Prettier installation is found.
/// ///
/// Default: {} /// Default: off
#[serde(default)] #[serde(default)]
pub prettier: Option<HashMap<String, serde_json::Value>>, pub prettier: Option<PrettierSettings>,
/// Whether to use language servers to provide code intelligence. /// Whether to use language servers to provide code intelligence.
/// ///
/// Default: true /// Default: true
@ -751,6 +749,30 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
merge(&mut settings.inlay_hints, src.inlay_hints); merge(&mut settings.inlay_hints, src.inlay_hints);
} }
/// Allows to enable/disable formatting with Prettier
/// and configure default Prettier, used when no project-level Prettier installation is found.
/// Prettier formatting is disabled by default.
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct PrettierSettings {
/// Enables or disables formatting with Prettier for a given language.
#[serde(default)]
pub allowed: bool,
/// Forces Prettier integration to use a specific parser name when formatting files with the language.
#[serde(default)]
pub parser: Option<String>,
/// Forces Prettier integration to use specific plugins when formatting files with the language.
/// The default Prettier will be installed with these plugins.
#[serde(default)]
pub plugins: HashSet<String>,
/// Default Prettier options, in the format as in package.json section for Prettier.
/// If project installs Prettier via its package.json, these options will be ignored.
#[serde(flatten)]
pub options: HashMap<String, serde_json::Value>,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -11,4 +11,3 @@ brackets = [
] ]
word_characters = ["-"] word_characters = ["-"]
block_comment = ["/* ", " */"] block_comment = ["/* ", " */"]
prettier_parser_name = "css"

View File

@ -17,7 +17,6 @@ brackets = [
word_characters = ["$", "#"] word_characters = ["$", "#"]
tab_size = 2 tab_size = 2
scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"] scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"]
prettier_parser_name = "babel"
[overrides.element] [overrides.element]
line_comments = { remove = true } line_comments = { remove = true }

View File

@ -8,5 +8,4 @@ brackets = [
{ start = "[", end = "]", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
] ]
prettier_parser_name = "json"
tab_size = 2 tab_size = 2

View File

@ -11,7 +11,6 @@ brackets = [
{ start = "'", end = "'", close = false, newline = false }, { start = "'", end = "'", close = false, newline = false },
{ start = "`", end = "`", close = false, newline = false }, { start = "`", end = "`", close = false, newline = false },
] ]
prettier_parser_name = "markdown"
tab_size = 2 tab_size = 2
soft_wrap = "preferred_line_length" soft_wrap = "preferred_line_length"

View File

@ -15,7 +15,6 @@ brackets = [
] ]
word_characters = ["#", "$"] word_characters = ["#", "$"]
scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"] scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"]
prettier_parser_name = "typescript"
tab_size = 2 tab_size = 2
[overrides.element] [overrides.element]

View File

@ -14,5 +14,4 @@ brackets = [
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] }, { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
] ]
word_characters = ["#", "$"] word_characters = ["#", "$"]
prettier_parser_name = "typescript"
tab_size = 2 tab_size = 2

View File

@ -10,5 +10,4 @@ brackets = [
] ]
increase_indent_pattern = ":\\s*[|>]?\\s*$" increase_indent_pattern = ":\\s*[|>]?\\s*$"
prettier_parser_name = "yaml"
tab_size = 2 tab_size = 2

View File

@ -2,7 +2,7 @@ use anyhow::Context;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use fs::Fs; use fs::Fs;
use gpui::{AsyncAppContext, Model}; use gpui::{AsyncAppContext, Model};
use language::{language_settings::language_settings, Buffer, Diff, LanguageRegistry}; use language::{language_settings::language_settings, Buffer, Diff};
use lsp::{LanguageServer, LanguageServerId}; use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -25,7 +25,6 @@ pub struct RealPrettier {
default: bool, default: bool,
prettier_dir: PathBuf, prettier_dir: PathBuf,
server: Arc<LanguageServer>, server: Arc<LanguageServer>,
language_registry: Arc<LanguageRegistry>,
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -156,7 +155,6 @@ impl Prettier {
_: LanguageServerId, _: LanguageServerId,
prettier_dir: PathBuf, prettier_dir: PathBuf,
_: Arc<dyn NodeRuntime>, _: Arc<dyn NodeRuntime>,
_: Arc<LanguageRegistry>,
_: AsyncAppContext, _: AsyncAppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Ok(Self::Test(TestPrettier { Ok(Self::Test(TestPrettier {
@ -170,7 +168,6 @@ impl Prettier {
server_id: LanguageServerId, server_id: LanguageServerId,
prettier_dir: PathBuf, prettier_dir: PathBuf,
node: Arc<dyn NodeRuntime>, node: Arc<dyn NodeRuntime>,
language_registry: Arc<LanguageRegistry>,
cx: AsyncAppContext, cx: AsyncAppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
use lsp::LanguageServerBinary; use lsp::LanguageServerBinary;
@ -209,7 +206,6 @@ impl Prettier {
Ok(Self::Real(RealPrettier { Ok(Self::Real(RealPrettier {
server, server,
default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(), default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
language_registry,
prettier_dir, prettier_dir,
})) }))
} }
@ -225,22 +221,19 @@ impl Prettier {
let params = buffer let params = buffer
.update(cx, |buffer, cx| { .update(cx, |buffer, cx| {
let buffer_language = buffer.language(); let buffer_language = buffer.language();
let parser_with_plugins = buffer_language.and_then(|l| { let language_settings = language_settings(buffer_language, buffer.file(), cx);
let prettier_parser = l.prettier_parser_name()?; let prettier_settings = &language_settings.prettier;
let mut prettier_plugins = anyhow::ensure!(
local.language_registry.all_prettier_plugins(); prettier_settings.allowed,
prettier_plugins.dedup(); "Cannot format: prettier is not allowed for language {buffer_language:?}"
Some((prettier_parser, prettier_plugins)) );
});
let prettier_node_modules = self.prettier_dir().join("node_modules"); let prettier_node_modules = self.prettier_dir().join("node_modules");
anyhow::ensure!( anyhow::ensure!(
prettier_node_modules.is_dir(), prettier_node_modules.is_dir(),
"Prettier node_modules dir does not exist: {prettier_node_modules:?}" "Prettier node_modules dir does not exist: {prettier_node_modules:?}"
); );
let plugin_name_into_path = |plugin_name: Arc<str>| { let plugin_name_into_path = |plugin_name: &str| {
let prettier_plugin_dir = let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
prettier_node_modules.join(plugin_name.as_ref());
[ [
prettier_plugin_dir.join("dist").join("index.mjs"), prettier_plugin_dir.join("dist").join("index.mjs"),
prettier_plugin_dir.join("dist").join("index.js"), prettier_plugin_dir.join("dist").join("index.js"),
@ -255,45 +248,34 @@ impl Prettier {
.into_iter() .into_iter()
.find(|possible_plugin_path| possible_plugin_path.is_file()) .find(|possible_plugin_path| possible_plugin_path.is_file())
}; };
let (parser, located_plugins) = match 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 // Tailwind plugin requires being added last
.into_iter() // https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
.filter(|plugin_name| { let mut add_tailwind_back = false;
if plugin_name.as_ref()
== TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME let mut located_plugins = prettier_settings.plugins.iter()
{ .filter(|plugin_name| {
add_tailwind_back = true; if plugin_name.as_str() == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
false add_tailwind_back = true;
} else { false
true } else {
} true
})
.map(|plugin_name| {
(plugin_name.clone(), plugin_name_into_path(plugin_name))
})
.collect::<Vec<_>>();
if add_tailwind_back {
plugins.push((
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME.into(),
plugin_name_into_path(
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME.into(),
),
));
} }
(Some(parser.to_string()), plugins) })
} .map(|plugin_name| {
None => (None, Vec::new()), let plugin_path = plugin_name_into_path(plugin_name);
}; (plugin_name.clone(), plugin_path)
})
.collect::<Vec<_>>();
if add_tailwind_back {
located_plugins.push((
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME.to_owned(),
plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME),
));
}
let prettier_options = if self.is_default() { let prettier_options = if self.is_default() {
let language_settings = let mut options = prettier_settings.options.clone();
language_settings(buffer_language, buffer.file(), cx);
let mut options = language_settings.prettier.clone();
if !options.contains_key("tabWidth") { if !options.contains_key("tabWidth") {
options.insert( options.insert(
"tabWidth".to_string(), "tabWidth".to_string(),
@ -321,11 +303,7 @@ impl Prettier {
match located_plugin_path { match located_plugin_path {
Some(path) => Some(path), Some(path) => Some(path),
None => { None => {
log::error!( log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}");
"Have not found plugin path for {:?} inside {:?}",
plugin_name,
prettier_node_modules
);
None None
} }
} }
@ -341,7 +319,7 @@ impl Prettier {
anyhow::Ok(FormatParams { anyhow::Ok(FormatParams {
text: buffer.text(), text: buffer.text(),
options: FormatOptions { options: FormatOptions {
parser, parser: prettier_settings.parser.clone(),
plugins, plugins,
path: buffer_path, path: buffer_path,
prettier_options, prettier_options,
@ -360,9 +338,19 @@ impl Prettier {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
Self::Test(_) => Ok(buffer Self::Test(_) => Ok(buffer
.update(cx, |buffer, cx| { .update(cx, |buffer, cx| {
let formatted_text = buffer.text() + FORMAT_SUFFIX; match buffer
buffer.diff(formatted_text, cx) .language()
})? .map(|language| language.lsp_id())
.as_deref()
{
Some("rust") => anyhow::bail!("prettier does not support Rust"),
Some(_other) => {
let formatted_text = buffer.text() + FORMAT_SUFFIX;
Ok(buffer.diff(formatted_text, cx))
}
None => panic!("Should not format buffer without a language with prettier"),
}
})??
.await), .await),
} }
} }

View File

@ -14,7 +14,7 @@ use futures::{
use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel}; use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
use language::{ use language::{
language_settings::{Formatter, LanguageSettings}, language_settings::{Formatter, LanguageSettings},
Buffer, Language, LanguageServerName, LocalFile, Buffer, LanguageServerName, LocalFile,
}; };
use lsp::{LanguageServer, LanguageServerId}; use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
@ -25,20 +25,12 @@ use crate::{
Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId, Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,
}; };
pub fn prettier_plugins_for_language<'a>( pub fn prettier_plugins_for_language(
language: &'a Arc<Language>,
language_settings: &LanguageSettings, language_settings: &LanguageSettings,
) -> Option<&'a Vec<Arc<str>>> { ) -> Option<&HashSet<String>> {
match &language_settings.formatter { match &language_settings.formatter {
Formatter::Prettier { .. } | Formatter::Auto => {} Formatter::Prettier { .. } | Formatter::Auto => Some(&language_settings.prettier.plugins),
Formatter::LanguageServer | Formatter::External { .. } | Formatter::CodeActions(_) => { Formatter::LanguageServer | Formatter::External { .. } | Formatter::CodeActions(_) => None,
return None
}
};
if language.prettier_parser_name().is_some() {
Some(language.prettier_plugins())
} else {
None
} }
} }
@ -110,6 +102,7 @@ pub struct DefaultPrettier {
installed_plugins: HashSet<Arc<str>>, installed_plugins: HashSet<Arc<str>>,
} }
#[derive(Debug)]
pub enum PrettierInstallation { pub enum PrettierInstallation {
NotInstalled { NotInstalled {
attempts: usize, attempts: usize,
@ -121,7 +114,7 @@ pub enum PrettierInstallation {
pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>; pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
#[derive(Clone)] #[derive(Debug, Clone)]
pub struct PrettierInstance { pub struct PrettierInstance {
attempt: usize, attempt: usize,
prettier: Option<PrettierTask>, prettier: Option<PrettierTask>,
@ -295,20 +288,15 @@ fn start_prettier(
) -> PrettierTask { ) -> PrettierTask {
cx.spawn(|project, mut cx| async move { cx.spawn(|project, mut cx| async move {
log::info!("Starting prettier at path {prettier_dir:?}"); log::info!("Starting prettier at path {prettier_dir:?}");
let language_registry = project.update(&mut cx, |project, _| project.languages.clone())?; let new_server_id = project.update(&mut cx, |project, _| {
let new_server_id = language_registry.next_language_server_id(); project.languages.next_language_server_id()
})?;
let new_prettier = Prettier::start( let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
new_server_id, .await
prettier_dir, .context("default prettier spawn")
node, .map(Arc::new)
language_registry, .map_err(Arc::new)?;
cx.clone(),
)
.await
.context("default prettier spawn")
.map(Arc::new)
.map_err(Arc::new)?;
register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx); register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
Ok(new_prettier) Ok(new_prettier)
}) })
@ -526,10 +514,7 @@ impl Project {
} }
let buffer = buffer.read(cx); let buffer = buffer.read(cx);
let buffer_file = buffer.file(); let buffer_file = buffer.file();
let Some(buffer_language) = buffer.language() else { if buffer.language().is_none() {
return Task::ready(None);
};
if buffer_language.prettier_parser_name().is_none() {
return Task::ready(None); return Task::ready(None);
} }
let Some(node) = self.node.clone() else { let Some(node) = self.node.clone() else {

View File

@ -550,6 +550,7 @@ pub enum FormatTrigger {
// Currently, formatting operations are represented differently depending on // Currently, formatting operations are represented differently depending on
// whether they come from a language server or an external command. // whether they come from a language server or an external command.
#[derive(Debug)]
enum FormatOperation { enum FormatOperation {
Lsp(Vec<(Range<Anchor>, String)>), Lsp(Vec<(Range<Anchor>, String)>),
External(Diff), External(Diff),
@ -1088,11 +1089,8 @@ impl Project {
.push((file.worktree.clone(), Arc::clone(language))); .push((file.worktree.clone(), Arc::clone(language)));
} }
} }
language_formatters_to_check.push(( language_formatters_to_check
buffer_file.map(|f| f.worktree_id(cx)), .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
Arc::clone(language),
settings.clone(),
));
} }
} }
} }
@ -1148,9 +1146,9 @@ impl Project {
} }
let mut prettier_plugins_by_worktree = HashMap::default(); let mut prettier_plugins_by_worktree = HashMap::default();
for (worktree, language, settings) in language_formatters_to_check { for (worktree, language_settings) in language_formatters_to_check {
if let Some(plugins) = if let Some(plugins) =
prettier_support::prettier_plugins_for_language(&language, &settings) prettier_support::prettier_plugins_for_language(&language_settings)
{ {
prettier_plugins_by_worktree prettier_plugins_by_worktree
.entry(worktree) .entry(worktree)
@ -1159,7 +1157,11 @@ impl Project {
} }
} }
for (worktree, prettier_plugins) in prettier_plugins_by_worktree { for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
self.install_default_prettier(worktree, prettier_plugins.into_iter(), cx); self.install_default_prettier(
worktree,
prettier_plugins.into_iter().map(Arc::from),
cx,
);
} }
// Start all the newly-enabled language servers. // Start all the newly-enabled language servers.
@ -3070,10 +3072,12 @@ impl Project {
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();
let buffer_file = File::from_dyn(buffer_file.as_ref()); let buffer_file = File::from_dyn(buffer_file.as_ref());
let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx)); let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
if let Some(prettier_plugins) = if let Some(prettier_plugins) = prettier_support::prettier_plugins_for_language(&settings) {
prettier_support::prettier_plugins_for_language(&new_language, &settings) self.install_default_prettier(
{ worktree,
self.install_default_prettier(worktree, prettier_plugins.iter().cloned(), cx); prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
cx,
);
}; };
if let Some(file) = buffer_file { if let Some(file) = buffer_file {
let worktree = file.worktree.clone(); let worktree = file.worktree.clone();
@ -4786,6 +4790,11 @@ impl Project {
.zip(buffer_abs_path.as_ref()); .zip(buffer_abs_path.as_ref());
let mut format_operation = None; let mut format_operation = None;
let prettier_settings = buffer.read_with(&mut cx, |buffer, cx| {
language_settings(buffer.language(), buffer.file(), cx)
.prettier
.clone()
})?;
match (&settings.formatter, &settings.format_on_save) { match (&settings.formatter, &settings.format_on_save) {
(_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {} (_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
@ -4845,11 +4854,18 @@ impl Project {
} }
} }
(Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => { (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
let prettier = let prettier = if prettier_settings.allowed {
prettier_support::format_with_prettier(&project, buffer, &mut cx).await; prettier_support::format_with_prettier(&project, buffer, &mut cx)
.await
.transpose()
.ok()
.flatten()
} else {
None
};
if let Some(operation) = prettier { if let Some(operation) = prettier {
format_operation = Some(operation?); format_operation = Some(operation);
} else if let Some((language_server, buffer_abs_path)) = server_and_buffer { } else if let Some((language_server, buffer_abs_path)) = server_and_buffer {
format_operation = Some(FormatOperation::Lsp( format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp( Self::format_via_lsp(
@ -4866,11 +4882,12 @@ impl Project {
} }
} }
(Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => { (Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => {
let prettier = if prettier_settings.allowed {
prettier_support::format_with_prettier(&project, buffer, &mut cx).await; if let Some(operation) =
prettier_support::format_with_prettier(&project, buffer, &mut cx).await
if let Some(operation) = prettier { {
format_operation = Some(operation?); format_operation = Some(operation?);
}
} }
} }
}; };