Allow extensions to define more of the methods in the LspAdapter trait (#9554)
Our goal is to extract Svelte support into an extension, since we've seen problems with the Tree-sitter Svelte parser crashing due to bugs in the external scanner. In order to do this, we need a couple more capabilities in LSP extensions: * [x] `initialization_options` - programmatically controlling the JSON initialization params sent to the language server * [x] `prettier_plugins` - statically specifying a list of prettier plugins that apply for a given language. * [x] `npm_install_package` Release Notes: - N/A --------- Co-authored-by: Marshall <marshall@zed.dev> Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
parent
0ce5cdc48f
commit
d699b8e104
26 changed files with 318 additions and 208 deletions
|
@ -92,4 +92,37 @@ impl LspAdapter for ExtensionLspAdapter {
|
|||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let delegate = delegate.clone();
|
||||
let json_options = self
|
||||
.extension
|
||||
.call({
|
||||
let this = self.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let resource = store.data_mut().table().push(delegate)?;
|
||||
let options = extension
|
||||
.call_language_server_initialization_options(
|
||||
store,
|
||||
&this.config,
|
||||
resource,
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| anyhow!("{}", e))?;
|
||||
anyhow::Ok(options)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
Ok(if let Some(json_options) = json_options {
|
||||
serde_json::from_str(&json_options)?
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -307,6 +307,46 @@ impl wit::ExtensionImports for WasmState {
|
|||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
) -> wasmtime::Result<Result<Option<String>, String>> {
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
package_name: String,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
this.host
|
||||
.node_runtime
|
||||
.npm_package_installed_version(&this.host.work_dir, &package_name)
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(inner(self, package_name)
|
||||
.await
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
|
||||
async fn npm_install_package(
|
||||
&mut self,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> wasmtime::Result<Result<(), String>> {
|
||||
async fn inner(
|
||||
this: &mut WasmState,
|
||||
package_name: String,
|
||||
version: String,
|
||||
) -> anyhow::Result<()> {
|
||||
this.host
|
||||
.node_runtime
|
||||
.npm_install_packages(&this.host.work_dir, &[(&package_name, &version)])
|
||||
.await
|
||||
}
|
||||
|
||||
Ok(inner(self, package_name, version)
|
||||
.await
|
||||
.map_err(|err| err.to_string()))
|
||||
}
|
||||
|
||||
async fn latest_github_release(
|
||||
&mut self,
|
||||
repo: String,
|
||||
|
|
|
@ -13,6 +13,14 @@ pub trait Extension: Send + Sync {
|
|||
config: wit::LanguageServerConfig,
|
||||
worktree: &wit::Worktree,
|
||||
) -> Result<Command>;
|
||||
|
||||
fn language_server_initialization_options(
|
||||
&mut self,
|
||||
_config: wit::LanguageServerConfig,
|
||||
_worktree: &wit::Worktree,
|
||||
) -> Result<Option<String>> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
|
@ -60,4 +68,11 @@ impl wit::Guest for Component {
|
|||
) -> Result<wit::Command> {
|
||||
extension().language_server_command(config, worktree)
|
||||
}
|
||||
|
||||
fn language_server_initialization_options(
|
||||
config: LanguageServerConfig,
|
||||
worktree: &Worktree,
|
||||
) -> Result<Option<String>, String> {
|
||||
extension().language_server_initialization_options(config, worktree)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,12 @@ world extension {
|
|||
/// Gets the latest version of the given NPM package.
|
||||
import npm-package-latest-version: func(package-name: string) -> result<string, string>;
|
||||
|
||||
/// Returns the installed version of the given NPM package, if it exists.
|
||||
import npm-package-installed-version: func(package-name: string) -> result<option<string>, string>;
|
||||
|
||||
/// Installs the specified NPM package.
|
||||
import npm-install-package: func(package-name: string, version: string) -> result<_, string>;
|
||||
|
||||
/// Gets the latest release for the given GitHub repository.
|
||||
import latest-github-release: func(repo: string, options: github-release-options) -> result<github-release, string>;
|
||||
|
||||
|
@ -81,4 +87,5 @@ world extension {
|
|||
}
|
||||
|
||||
export language-server-command: func(config: language-server-config, worktree: borrow<worktree>) -> result<command, string>;
|
||||
export language-server-initialization-options: func(config: language-server-config, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
}
|
||||
|
|
|
@ -259,10 +259,6 @@ impl CachedLspAdapter {
|
|||
self.adapter.label_for_symbol(name, kind, language).await
|
||||
}
|
||||
|
||||
pub fn prettier_plugins(&self) -> &[&'static str] {
|
||||
self.adapter.prettier_plugins()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> Option<&FakeLspAdapter> {
|
||||
self.adapter.as_fake()
|
||||
|
@ -441,8 +437,11 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
}
|
||||
|
||||
/// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp::InitializeParams`]
|
||||
fn initialization_options(&self) -> Option<Value> {
|
||||
None
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<Value>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn workspace_configuration(&self, _workspace_root: &Path, _cx: &mut AppContext) -> Value {
|
||||
|
@ -472,10 +471,6 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&[]
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_fake(&self) -> Option<&FakeLspAdapter> {
|
||||
None
|
||||
|
@ -575,6 +570,9 @@ pub struct LanguageConfig {
|
|||
/// 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>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)]
|
||||
|
@ -656,6 +654,7 @@ impl Default for LanguageConfig {
|
|||
overrides: Default::default(),
|
||||
word_characters: Default::default(),
|
||||
prettier_parser_name: None,
|
||||
prettier_plugins: Default::default(),
|
||||
collapsed_placeholder: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -1283,6 +1282,10 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageScope {
|
||||
|
@ -1547,12 +1550,11 @@ impl LspAdapter for FakeLspAdapter {
|
|||
self.disk_based_diagnostics_progress_token.clone()
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<Value> {
|
||||
self.initialization_options.clone()
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&self.prettier_plugins
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<Value>> {
|
||||
Ok(self.initialization_options.clone())
|
||||
}
|
||||
|
||||
fn as_fake(&self) -> Option<&FakeLspAdapter> {
|
||||
|
|
|
@ -63,7 +63,7 @@ pub enum LanguageServerBinaryStatus {
|
|||
|
||||
pub struct PendingLanguageServer {
|
||||
pub server_id: LanguageServerId,
|
||||
pub task: Task<Result<lsp::LanguageServer>>,
|
||||
pub task: Task<Result<(lsp::LanguageServer, Option<serde_json::Value>)>>,
|
||||
pub container_dir: Option<Arc<Path>>,
|
||||
}
|
||||
|
||||
|
@ -629,6 +629,15 @@ impl LanguageRegistry {
|
|||
.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(
|
||||
&self,
|
||||
server_name: LanguageServerName,
|
||||
|
@ -680,6 +689,12 @@ impl LanguageRegistry {
|
|||
)
|
||||
.await?;
|
||||
|
||||
let options = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
.initialization_options(&delegate)
|
||||
.await?;
|
||||
|
||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||
task.await?;
|
||||
}
|
||||
|
@ -727,18 +742,21 @@ impl LanguageRegistry {
|
|||
})
|
||||
.detach();
|
||||
|
||||
return Ok(server);
|
||||
return Ok((server, options));
|
||||
}
|
||||
|
||||
drop(this);
|
||||
lsp::LanguageServer::new(
|
||||
stderr_capture,
|
||||
server_id,
|
||||
binary,
|
||||
&root_path,
|
||||
adapter.code_action_kinds(),
|
||||
cx,
|
||||
)
|
||||
Ok((
|
||||
lsp::LanguageServer::new(
|
||||
stderr_capture,
|
||||
server_id,
|
||||
binary,
|
||||
&root_path,
|
||||
adapter.code_action_kinds(),
|
||||
cx,
|
||||
)?,
|
||||
options,
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -90,17 +90,16 @@ impl LspAdapter for AstroLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true,
|
||||
"typescript": {
|
||||
"tsdk": "node_modules/typescript/lib",
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&["prettier-plugin-astro"]
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ brackets = [
|
|||
word_characters = ["#", "$", "-"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "astro"
|
||||
prettier_plugins = ["prettier-plugin-astro"]
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
|
|
|
@ -91,10 +91,13 @@ impl LspAdapter for CssLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -188,10 +188,13 @@ impl LspAdapter for DenoLspAdapter {
|
|||
})
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true,
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
|
|
|
@ -189,8 +189,11 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
})
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"usePlaceholders": true,
|
||||
"hints": {
|
||||
"assignVariableTypes": true,
|
||||
|
@ -201,7 +204,7 @@ impl super::LspAdapter for GoLspAdapter {
|
|||
"parameterNames": true,
|
||||
"rangeVariableTypes": true
|
||||
}
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
|
|
|
@ -91,10 +91,13 @@ impl LspAdapter for HtmlLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -143,10 +143,13 @@ impl LspAdapter for JsonLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn workspace_configuration(&self, _workspace_root: &Path, cx: &mut AppContext) -> Value {
|
||||
|
|
|
@ -97,24 +97,9 @@ impl LspAdapter for IntelephenseLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
_item: &lsp::CompletionItem,
|
||||
_language: &Arc<language::Language>,
|
||||
) -> Option<language::CodeLabel> {
|
||||
None
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
None
|
||||
}
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
HashMap::from_iter([("PHP".into(), "php".into())])
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&["@prettier/plugin-php"]
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
|
|
|
@ -15,3 +15,4 @@ collapsed_placeholder = "/* ... */"
|
|||
word_characters = ["$"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "php"
|
||||
prettier_plugins = ["@prettier/plugin-php"]
|
||||
|
|
|
@ -88,10 +88,6 @@ impl LspAdapter for PrismaLspAdapter {
|
|||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
|
|
|
@ -93,12 +93,15 @@ impl LspAdapter for PurescriptLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"purescript": {
|
||||
"addSpagoSources": true
|
||||
}
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
|
|
|
@ -90,7 +90,10 @@ impl LspAdapter for SvelteLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let config = json!({
|
||||
"inlayHints": {
|
||||
"parameterNames": {
|
||||
|
@ -116,17 +119,13 @@ impl LspAdapter for SvelteLspAdapter {
|
|||
}
|
||||
});
|
||||
|
||||
Some(json!({
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true,
|
||||
"configuration": {
|
||||
"typescript": config,
|
||||
"javascript": config
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&["prettier-plugin-svelte"]
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ brackets = [
|
|||
]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "svelte"
|
||||
prettier_plugins = ["prettier-plugin-svelte"]
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
|
|
|
@ -92,8 +92,11 @@ impl LspAdapter for TailwindLspAdapter {
|
|||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true,
|
||||
"userLanguages": {
|
||||
"html": "html",
|
||||
|
@ -101,7 +104,7 @@ impl LspAdapter for TailwindLspAdapter {
|
|||
"javascript": "javascript",
|
||||
"typescriptreact": "typescriptreact",
|
||||
},
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn workspace_configuration(&self, _workspace_root: &Path, _: &mut AppContext) -> Value {
|
||||
|
@ -126,10 +129,6 @@ impl LspAdapter for TailwindLspAdapter {
|
|||
("PHP".to_string(), "php".to_string()),
|
||||
])
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
|
|
|
@ -164,8 +164,11 @@ impl LspAdapter for TypeScriptLspAdapter {
|
|||
})
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(Some(json!({
|
||||
"provideFormatter": true,
|
||||
"tsserver": {
|
||||
"path": "node_modules/typescript/lib",
|
||||
|
@ -180,7 +183,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
|||
"includeInlayFunctionLikeReturnTypeHints": true,
|
||||
"includeInlayEnumMemberValueHints": true,
|
||||
}
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
|
@ -367,18 +370,6 @@ impl LspAdapter for EsLintLspAdapter {
|
|||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_eslint_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
_item: &lsp::CompletionItem,
|
||||
_language: &Arc<language::Language>,
|
||||
) -> Option<language::CodeLabel> {
|
||||
None
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_eslint_server_binary(
|
||||
|
|
|
@ -5,7 +5,6 @@ pub use language::*;
|
|||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::Value;
|
||||
use smol::fs::{self};
|
||||
use std::{
|
||||
any::Any,
|
||||
|
@ -56,17 +55,20 @@ impl super::LspAdapter for VueLspAdapter {
|
|||
ts_version: self.node.npm_package_latest_version("typescript").await?,
|
||||
}) as Box<_>)
|
||||
}
|
||||
fn initialization_options(&self) -> Option<Value> {
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let typescript_sdk_path = self.typescript_install_path.lock();
|
||||
let typescript_sdk_path = typescript_sdk_path
|
||||
.as_ref()
|
||||
.expect("initialization_options called without a container_dir for typescript");
|
||||
|
||||
Some(serde_json::json!({
|
||||
Ok(Some(serde_json::json!({
|
||||
"typescript": {
|
||||
"tsdk": typescript_sdk_path
|
||||
}
|
||||
}))
|
||||
})))
|
||||
}
|
||||
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||
// REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it
|
||||
|
|
|
@ -4,8 +4,8 @@ use async_tar::Archive;
|
|||
use futures::AsyncReadExt;
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use smol::{fs, io::BufReader, lock::Mutex, process::Command};
|
||||
use std::io;
|
||||
use std::process::{Output, Stdio};
|
||||
use std::{
|
||||
env::consts,
|
||||
|
@ -46,6 +46,12 @@ pub trait NodeRuntime: Send + Sync {
|
|||
async fn npm_install_packages(&self, directory: &Path, packages: &[(&str, &str)])
|
||||
-> Result<()>;
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&self,
|
||||
local_package_directory: &PathBuf,
|
||||
name: &str,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn should_install_npm_package(
|
||||
&self,
|
||||
package_name: &str,
|
||||
|
@ -60,36 +66,19 @@ pub trait NodeRuntime: Send + Sync {
|
|||
return true;
|
||||
}
|
||||
|
||||
let package_json_path = local_package_directory.join("package.json");
|
||||
|
||||
let mut contents = String::new();
|
||||
|
||||
let Some(mut file) = fs::File::open(package_json_path).await.log_err() else {
|
||||
let Some(installed_version) = self
|
||||
.npm_package_installed_version(local_package_directory, package_name)
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
else {
|
||||
return true;
|
||||
};
|
||||
|
||||
file.read_to_string(&mut contents).await.log_err();
|
||||
|
||||
let Some(package_json): Option<Value> = serde_json::from_str(&contents).log_err() else {
|
||||
let Some(installed_version) = Version::parse(&installed_version).log_err() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let installed_version = package_json
|
||||
.get("dependencies")
|
||||
.and_then(|deps| deps.get(package_name))
|
||||
.and_then(|server_name| server_name.as_str());
|
||||
|
||||
let Some(installed_version) = installed_version else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(latest_version) = Version::parse(latest_version).log_err() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let installed_version = installed_version.trim_start_matches(|c: char| !c.is_ascii_digit());
|
||||
|
||||
let Some(installed_version) = Version::parse(installed_version).log_err() else {
|
||||
let Some(latest_version) = Version::parse(&latest_version).log_err() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -281,6 +270,36 @@ impl NodeRuntime for RealNodeRuntime {
|
|||
.ok_or_else(|| anyhow!("no version found for npm package {}", name))
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&self,
|
||||
local_package_directory: &PathBuf,
|
||||
name: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let mut package_json_path = local_package_directory.clone();
|
||||
package_json_path.extend(["node_modules", name, "package.json"]);
|
||||
|
||||
let mut file = match fs::File::open(package_json_path).await {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Err(err)?
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PackageJson {
|
||||
version: String,
|
||||
}
|
||||
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
let package_json: PackageJson = serde_json::from_str(&contents)?;
|
||||
Ok(Some(package_json.version))
|
||||
}
|
||||
|
||||
async fn npm_install_packages(
|
||||
&self,
|
||||
directory: &Path,
|
||||
|
@ -335,6 +354,14 @@ impl NodeRuntime for FakeNodeRuntime {
|
|||
unreachable!("Should not query npm package '{name}' for latest version")
|
||||
}
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
&self,
|
||||
_local_package_directory: &PathBuf,
|
||||
name: &str,
|
||||
) -> Result<Option<String>> {
|
||||
unreachable!("Should not query npm package '{name}' for installed version")
|
||||
}
|
||||
|
||||
async fn npm_install_packages(
|
||||
&self,
|
||||
_: &Path,
|
||||
|
|
|
@ -227,13 +227,8 @@ impl Prettier {
|
|||
let buffer_language = buffer.language();
|
||||
let parser_with_plugins = buffer_language.and_then(|l| {
|
||||
let prettier_parser = l.prettier_parser_name()?;
|
||||
let mut prettier_plugins = local
|
||||
.language_registry
|
||||
.lsp_adapters(l)
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.prettier_plugins())
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
let mut prettier_plugins =
|
||||
local.language_registry.all_prettier_plugins();
|
||||
prettier_plugins.dedup();
|
||||
Some((prettier_parser, prettier_plugins))
|
||||
});
|
||||
|
@ -243,8 +238,9 @@ impl Prettier {
|
|||
prettier_node_modules.is_dir(),
|
||||
"Prettier node_modules dir does not exist: {prettier_node_modules:?}"
|
||||
);
|
||||
let plugin_name_into_path = |plugin_name: &str| {
|
||||
let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
|
||||
let plugin_name_into_path = |plugin_name: Arc<str>| {
|
||||
let prettier_plugin_dir =
|
||||
prettier_node_modules.join(plugin_name.as_ref());
|
||||
[
|
||||
prettier_plugin_dir.join("dist").join("index.mjs"),
|
||||
prettier_plugin_dir.join("dist").join("index.js"),
|
||||
|
@ -267,8 +263,10 @@ impl Prettier {
|
|||
|
||||
let mut plugins = plugins
|
||||
.into_iter()
|
||||
.filter(|&plugin_name| {
|
||||
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
|
||||
.filter(|plugin_name| {
|
||||
if plugin_name.as_ref()
|
||||
== TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME
|
||||
{
|
||||
add_tailwind_back = true;
|
||||
false
|
||||
} else {
|
||||
|
@ -276,14 +274,14 @@ impl Prettier {
|
|||
}
|
||||
})
|
||||
.map(|plugin_name| {
|
||||
(plugin_name, plugin_name_into_path(plugin_name))
|
||||
(plugin_name.clone(), plugin_name_into_path(plugin_name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if add_tailwind_back {
|
||||
plugins.push((
|
||||
&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
|
||||
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME.into(),
|
||||
plugin_name_into_path(
|
||||
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
|
||||
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME.into(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use futures::{
|
|||
use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
|
||||
use language::{
|
||||
language_settings::{Formatter, LanguageSettings},
|
||||
Buffer, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
Buffer, Language, LanguageServerName, LocalFile,
|
||||
};
|
||||
use lsp::{LanguageServer, LanguageServerId};
|
||||
use node_runtime::NodeRuntime;
|
||||
|
@ -25,28 +25,19 @@ use crate::{
|
|||
Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,
|
||||
};
|
||||
|
||||
pub fn prettier_plugins_for_language(
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
language: &Arc<Language>,
|
||||
pub fn prettier_plugins_for_language<'a>(
|
||||
language: &'a Arc<Language>,
|
||||
language_settings: &LanguageSettings,
|
||||
) -> Option<HashSet<&'static str>> {
|
||||
) -> Option<&'a Vec<Arc<str>>> {
|
||||
match &language_settings.formatter {
|
||||
Formatter::Prettier { .. } | Formatter::Auto => {}
|
||||
Formatter::LanguageServer | Formatter::External { .. } => return None,
|
||||
};
|
||||
let mut prettier_plugins = None;
|
||||
if language.prettier_parser_name().is_some() {
|
||||
prettier_plugins
|
||||
.get_or_insert_with(|| HashSet::default())
|
||||
.extend(
|
||||
language_registry
|
||||
.lsp_adapters(language)
|
||||
.iter()
|
||||
.flat_map(|adapter| adapter.prettier_plugins()),
|
||||
)
|
||||
Some(language.prettier_plugins())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
prettier_plugins
|
||||
}
|
||||
|
||||
pub(super) async fn format_with_prettier(
|
||||
|
@ -114,14 +105,14 @@ pub(super) async fn format_with_prettier(
|
|||
|
||||
pub struct DefaultPrettier {
|
||||
prettier: PrettierInstallation,
|
||||
installed_plugins: HashSet<&'static str>,
|
||||
installed_plugins: HashSet<Arc<str>>,
|
||||
}
|
||||
|
||||
pub enum PrettierInstallation {
|
||||
NotInstalled {
|
||||
attempts: usize,
|
||||
installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
|
||||
not_installed_plugins: HashSet<&'static str>,
|
||||
not_installed_plugins: HashSet<Arc<str>>,
|
||||
},
|
||||
Installed(PrettierInstance),
|
||||
}
|
||||
|
@ -376,12 +367,14 @@ fn register_new_prettier(
|
|||
|
||||
async fn install_prettier_packages(
|
||||
fs: &dyn Fs,
|
||||
plugins_to_install: HashSet<&'static str>,
|
||||
plugins_to_install: HashSet<Arc<str>>,
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
) -> anyhow::Result<()> {
|
||||
let packages_to_versions =
|
||||
future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
|
||||
|package_name| async {
|
||||
let packages_to_versions = future::try_join_all(
|
||||
plugins_to_install
|
||||
.iter()
|
||||
.chain(Some(&"prettier".into()))
|
||||
.map(|package_name| async {
|
||||
let returned_package_name = package_name.to_string();
|
||||
let latest_version = node
|
||||
.npm_package_latest_version(package_name)
|
||||
|
@ -390,10 +383,10 @@ async fn install_prettier_packages(
|
|||
format!("fetching latest npm version for package {returned_package_name}")
|
||||
})?;
|
||||
anyhow::Ok((returned_package_name, latest_version))
|
||||
},
|
||||
))
|
||||
.await
|
||||
.context("fetching latest npm versions")?;
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.context("fetching latest npm versions")?;
|
||||
|
||||
let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path();
|
||||
match fs.metadata(default_prettier_dir).await.with_context(|| {
|
||||
|
@ -639,32 +632,22 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn install_default_prettier(
|
||||
&mut self,
|
||||
_worktree: Option<WorktreeId>,
|
||||
plugins: HashSet<&'static str>,
|
||||
_cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
// suppress unused code warnings
|
||||
let _ = should_write_prettier_server_file;
|
||||
let _ = install_prettier_packages;
|
||||
let _ = save_prettier_server_file;
|
||||
|
||||
self.default_prettier.installed_plugins.extend(plugins);
|
||||
self.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance {
|
||||
attempt: 0,
|
||||
prettier: None,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
pub fn install_default_prettier(
|
||||
&mut self,
|
||||
worktree: Option<WorktreeId>,
|
||||
mut new_plugins: HashSet<&'static str>,
|
||||
plugins: impl Iterator<Item = Arc<str>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if cfg!(any(test, feature = "test-support")) {
|
||||
self.default_prettier.installed_plugins.extend(plugins);
|
||||
self.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance {
|
||||
attempt: 0,
|
||||
prettier: None,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let mut new_plugins = plugins.collect::<HashSet<_>>();
|
||||
let Some(node) = self.node.as_ref().cloned() else {
|
||||
return;
|
||||
};
|
||||
|
@ -702,7 +685,7 @@ impl Project {
|
|||
);
|
||||
return;
|
||||
}
|
||||
new_plugins.extend(not_installed_plugins.iter());
|
||||
new_plugins.extend(not_installed_plugins.iter().cloned());
|
||||
installation_task.clone()
|
||||
}
|
||||
PrettierInstallation::Installed { .. } => {
|
||||
|
@ -735,7 +718,7 @@ impl Project {
|
|||
project.update(&mut cx, |project, _| {
|
||||
if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut project.default_prettier.prettier {
|
||||
*attempts += 1;
|
||||
new_plugins.extend(not_installed_plugins.iter());
|
||||
new_plugins.extend(not_installed_plugins.iter().cloned());
|
||||
installation_attempt = *attempts;
|
||||
needs_install = true;
|
||||
};
|
||||
|
@ -761,7 +744,7 @@ impl Project {
|
|||
not_installed_plugins.retain(|plugin| {
|
||||
!project.default_prettier.installed_plugins.contains(plugin)
|
||||
});
|
||||
not_installed_plugins.extend(new_plugins.iter());
|
||||
not_installed_plugins.extend(new_plugins.iter().cloned());
|
||||
}
|
||||
needs_install |= !new_plugins.is_empty();
|
||||
})?;
|
||||
|
|
|
@ -994,19 +994,17 @@ impl Project {
|
|||
|
||||
let mut prettier_plugins_by_worktree = HashMap::default();
|
||||
for (worktree, language, settings) in language_formatters_to_check {
|
||||
if let Some(plugins) = prettier_support::prettier_plugins_for_language(
|
||||
&self.languages,
|
||||
&language,
|
||||
&settings,
|
||||
) {
|
||||
if let Some(plugins) =
|
||||
prettier_support::prettier_plugins_for_language(&language, &settings)
|
||||
{
|
||||
prettier_plugins_by_worktree
|
||||
.entry(worktree)
|
||||
.or_insert_with(|| HashSet::default())
|
||||
.extend(plugins);
|
||||
.extend(plugins.iter().cloned());
|
||||
}
|
||||
}
|
||||
for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
|
||||
self.install_default_prettier(worktree, prettier_plugins, cx);
|
||||
self.install_default_prettier(worktree, prettier_plugins.into_iter(), cx);
|
||||
}
|
||||
|
||||
// Start all the newly-enabled language servers.
|
||||
|
@ -2845,12 +2843,10 @@ impl Project {
|
|||
let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
|
||||
let buffer_file = File::from_dyn(buffer_file.as_ref());
|
||||
let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
|
||||
if let Some(prettier_plugins) = prettier_support::prettier_plugins_for_language(
|
||||
&self.languages,
|
||||
&new_language,
|
||||
&settings,
|
||||
) {
|
||||
self.install_default_prettier(worktree, prettier_plugins, cx);
|
||||
if let Some(prettier_plugins) =
|
||||
prettier_support::prettier_plugins_for_language(&new_language, &settings)
|
||||
{
|
||||
self.install_default_prettier(worktree, prettier_plugins.iter().cloned(), cx);
|
||||
};
|
||||
if let Some(file) = buffer_file {
|
||||
let worktree = file.worktree.clone();
|
||||
|
@ -3104,7 +3100,7 @@ impl Project {
|
|||
) -> Result<Arc<LanguageServer>> {
|
||||
let workspace_config =
|
||||
cx.update(|cx| adapter.workspace_configuration(worktree_path, cx))?;
|
||||
let language_server = pending_server.task.await?;
|
||||
let (language_server, mut initialization_options) = pending_server.task.await?;
|
||||
|
||||
let name = language_server.name();
|
||||
language_server
|
||||
|
@ -3344,7 +3340,6 @@ impl Project {
|
|||
})
|
||||
.detach();
|
||||
|
||||
let mut initialization_options = adapter.adapter.initialization_options();
|
||||
match (&mut initialization_options, override_options) {
|
||||
(Some(initialization_options), Some(override_options)) => {
|
||||
merge_json_value_into(override_options, initialization_options);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue