Refactor prettier (#17977)

In preparation for making formatting work on ssh remotes

Release Notes:

- N/A

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Conrad Irwin 2024-09-17 16:37:56 -06:00 committed by GitHub
parent db18f7a2b0
commit 8e45bf71ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 911 additions and 705 deletions

View file

@ -462,3 +462,52 @@ impl NodeRuntime for FakeNodeRuntime {
unreachable!("Should not install packages {packages:?}")
}
}
// TODO: Remove this when headless binary can run node
pub struct DummyNodeRuntime;
impl DummyNodeRuntime {
pub fn new() -> Arc<dyn NodeRuntime> {
Arc::new(Self)
}
}
#[async_trait::async_trait]
impl NodeRuntime for DummyNodeRuntime {
async fn binary_path(&self) -> anyhow::Result<PathBuf> {
anyhow::bail!("Dummy Node Runtime")
}
async fn node_environment_path(&self) -> anyhow::Result<OsString> {
anyhow::bail!("Dummy node runtime")
}
async fn run_npm_subcommand(
&self,
_: Option<&Path>,
_subcommand: &str,
_args: &[&str],
) -> anyhow::Result<Output> {
anyhow::bail!("Dummy node runtime")
}
async fn npm_package_latest_version(&self, _name: &str) -> anyhow::Result<String> {
anyhow::bail!("Dummy node runtime")
}
async fn npm_package_installed_version(
&self,
_local_package_directory: &Path,
_name: &str,
) -> Result<Option<String>> {
anyhow::bail!("Dummy node runtime")
}
async fn npm_install_packages(
&self,
_: &Path,
_packages: &[(&str, &str)],
) -> anyhow::Result<()> {
anyhow::bail!("Dummy node runtime")
}
}

View file

@ -3,6 +3,7 @@ use crate::{
environment::ProjectEnvironment,
lsp_command::{self, *},
lsp_ext_command,
prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings},
relativize_path, resolve_path,
worktree_store::{WorktreeStore, WorktreeStoreEvent},
@ -101,6 +102,8 @@ pub struct LocalLspStore {
HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
supplementary_language_servers:
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
prettier_store: Model<PrettierStore>,
current_lsp_settings: HashMap<Arc<str>, LspSettings>,
_subscription: gpui::Subscription,
}
@ -135,6 +138,7 @@ impl RemoteLspStore {}
pub struct SshLspStore {
upstream_client: AnyProtoClient,
current_lsp_settings: HashMap<Arc<str>, LspSettings>,
}
#[allow(clippy::large_enum_variant)]
@ -310,9 +314,32 @@ impl LspStore {
}
}
pub fn swap_current_lsp_settings(
&mut self,
new_settings: HashMap<Arc<str>, LspSettings>,
) -> Option<HashMap<Arc<str>, LspSettings>> {
match &mut self.mode {
LspStoreMode::Ssh(SshLspStore {
current_lsp_settings,
..
})
| LspStoreMode::Local(LocalLspStore {
current_lsp_settings,
..
}) => {
let ret = mem::take(current_lsp_settings);
*current_lsp_settings = new_settings;
Some(ret)
}
LspStoreMode::Remote(_) => None,
}
}
#[allow(clippy::too_many_arguments)]
pub fn new_local(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
prettier_store: Model<PrettierStore>,
environment: Model<ProjectEnvironment>,
languages: Arc<LanguageRegistry>,
http_client: Option<Arc<dyn HttpClient>>,
@ -324,6 +351,10 @@ impl LspStore {
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
cx.subscribe(&prettier_store, Self::on_prettier_store_event)
.detach();
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
.detach();
Self {
mode: LspStoreMode::Local(LocalLspStore {
@ -332,6 +363,8 @@ impl LspStore {
last_workspace_edits_by_language_server: Default::default(),
language_server_watched_paths: Default::default(),
language_server_watcher_registrations: Default::default(),
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
prettier_store,
environment,
http_client,
fs,
@ -387,9 +420,14 @@ impl LspStore {
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
cx.observe_global::<SettingsStore>(Self::on_settings_changed)
.detach();
Self {
mode: LspStoreMode::Ssh(SshLspStore { upstream_client }),
mode: LspStoreMode::Ssh(SshLspStore {
upstream_client,
current_lsp_settings: Default::default(),
}),
downstream_client: None,
project_id,
buffer_store,
@ -401,6 +439,7 @@ impl LspStore {
buffer_snapshots: Default::default(),
next_diagnostic_group_id: Default::default(),
diagnostic_summaries: Default::default(),
diagnostics: Default::default(),
active_entry: None,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
@ -498,6 +537,36 @@ impl LspStore {
}
}
fn on_prettier_store_event(
&mut self,
_: Model<PrettierStore>,
event: &PrettierStoreEvent,
cx: &mut ModelContext<Self>,
) {
match event {
PrettierStoreEvent::LanguageServerRemoved(prettier_server_id) => {
self.unregister_supplementary_language_server(*prettier_server_id, cx);
}
PrettierStoreEvent::LanguageServerAdded {
new_server_id,
name,
prettier_server,
} => {
self.register_supplementary_language_server(
*new_server_id,
name.clone(),
prettier_server.clone(),
cx,
);
}
}
}
// todo!
pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
self.as_local().map(|local| local.prettier_store.clone())
}
fn on_buffer_event(
&mut self,
buffer: Model<Buffer>,
@ -656,11 +725,29 @@ impl LspStore {
});
let buffer_file = buffer.read(cx).file().cloned();
let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
let buffer_file = File::from_dyn(buffer_file.as_ref());
if let Some(file) = buffer_file {
let worktree_id = if let Some(file) = buffer_file {
let worktree = file.worktree.clone();
self.start_language_servers(&worktree, new_language.name(), cx)
self.start_language_servers(&worktree, new_language.name(), cx);
Some(worktree.read(cx).id())
} else {
None
};
if let Some(prettier_plugins) = prettier_store::prettier_plugins_for_language(&settings) {
let prettier_store = self.as_local().map(|s| s.prettier_store.clone());
if let Some(prettier_store) = prettier_store {
prettier_store.update(cx, |prettier_store, cx| {
prettier_store.install_default_prettier(
worktree_id,
prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
cx,
)
})
}
}
cx.emit(LspStoreEvent::LanguageDetected {
@ -799,6 +886,95 @@ impl LspStore {
Task::ready(Ok(Default::default()))
}
fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
let mut language_servers_to_start = Vec::new();
let mut language_formatters_to_check = Vec::new();
for buffer in self.buffer_store.read(cx).buffers() {
let buffer = buffer.read(cx);
let buffer_file = File::from_dyn(buffer.file());
let buffer_language = buffer.language();
let settings = language_settings(buffer_language, buffer.file(), cx);
if let Some(language) = buffer_language {
if settings.enable_language_server {
if let Some(file) = buffer_file {
language_servers_to_start.push((file.worktree.clone(), language.name()));
}
}
language_formatters_to_check
.push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
}
}
let mut language_servers_to_stop = Vec::new();
let mut language_servers_to_restart = Vec::new();
let languages = self.languages.to_vec();
let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
let Some(current_lsp_settings) = self.swap_current_lsp_settings(new_lsp_settings.clone())
else {
return;
};
for (worktree_id, started_lsp_name) in self.started_language_servers() {
let language = languages.iter().find_map(|l| {
let adapter = self
.languages
.lsp_adapters(&l.name())
.iter()
.find(|adapter| adapter.name == started_lsp_name)?
.clone();
Some((l, adapter))
});
if let Some((language, adapter)) = language {
let worktree = self.worktree_for_id(worktree_id, cx).ok();
let file = worktree.as_ref().and_then(|tree| {
tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
});
if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
} else if let Some(worktree) = worktree {
let server_name = &adapter.name.0;
match (
current_lsp_settings.get(server_name),
new_lsp_settings.get(server_name),
) {
(None, None) => {}
(Some(_), None) | (None, Some(_)) => {
language_servers_to_restart.push((worktree, language.name()));
}
(Some(current_lsp_settings), Some(new_lsp_settings)) => {
if current_lsp_settings != new_lsp_settings {
language_servers_to_restart.push((worktree, language.name()));
}
}
}
}
}
}
for (worktree_id, adapter_name) in language_servers_to_stop {
self.stop_language_server(worktree_id, adapter_name, cx)
.detach();
}
if let Some(prettier_store) = self.as_local().map(|s| s.prettier_store.clone()) {
prettier_store.update(cx, |prettier_store, cx| {
prettier_store.on_settings_changed(language_formatters_to_check, cx)
})
}
// Start all the newly-enabled language servers.
for (worktree, language) in language_servers_to_start {
self.start_language_servers(&worktree, language, cx);
}
// Restart all language servers with changed initialization options.
for (worktree, language) in language_servers_to_restart {
self.restart_language_servers(worktree, language, cx);
}
cx.notify();
}
pub async fn execute_code_actions_on_servers(
this: &WeakModel<LspStore>,
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
@ -2375,7 +2551,7 @@ impl LspStore {
})
}
pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
self.diagnostics.remove(&id_to_remove);
self.diagnostic_summaries.remove(&id_to_remove);
@ -2406,6 +2582,12 @@ impl LspStore {
}
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));
}
if let Some(local) = self.as_local() {
local.prettier_store.update(cx, |prettier_store, cx| {
prettier_store.remove_worktree(id_to_remove, cx);
})
}
}
pub fn shared(
@ -6117,6 +6299,10 @@ impl LspStore {
let Some(local) = self.as_local() else { return };
local.prettier_store.update(cx, |prettier_store, cx| {
prettier_store.update_prettier_settings(&worktree_handle, changes, cx)
});
let worktree_id = worktree_handle.read(cx).id();
let mut language_server_ids = self
.language_server_ids

View file

@ -4,7 +4,7 @@ pub mod debounced_delay;
pub mod lsp_command;
pub mod lsp_ext_command;
pub mod lsp_store;
mod prettier_support;
pub mod prettier_store;
pub mod project_settings;
pub mod search;
mod task_inventory;
@ -31,7 +31,6 @@ pub use environment::ProjectEnvironment;
use futures::{
channel::mpsc::{self, UnboundedReceiver},
future::try_join_all,
stream::FuturesUnordered,
AsyncWriteExt, FutureExt, StreamExt,
};
@ -59,8 +58,8 @@ use lsp_command::*;
use node_runtime::NodeRuntime;
use parking_lot::{Mutex, RwLock};
use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
use prettier_support::{DefaultPrettier, PrettierInstance};
use project_settings::{LspSettings, ProjectSettings, SettingsObserver};
pub use prettier_store::PrettierStore;
use project_settings::{ProjectSettings, SettingsObserver};
use remote::SshSession;
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
use search::{SearchInputKind, SearchQuery, SearchResult};
@ -140,7 +139,6 @@ pub struct Project {
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
languages: Arc<LanguageRegistry>,
client: Arc<client::Client>,
current_lsp_settings: HashMap<Arc<str>, LspSettings>,
join_project_response_message_id: u32,
user_store: Model<UserStore>,
fs: Arc<dyn Fs>,
@ -157,9 +155,6 @@ pub struct Project {
remotely_created_buffers: Arc<Mutex<RemotelyCreatedBuffers>>,
terminals: Terminals,
node: Option<Arc<dyn NodeRuntime>>,
default_prettier: DefaultPrettier,
prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
prettier_instances: HashMap<PathBuf, PrettierInstance>,
tasks: Model<Inventory>,
hosted_project_id: Option<ProjectId>,
dev_server_project_id: Option<client::DevServerProjectId>,
@ -634,6 +629,16 @@ impl Project {
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
.detach();
let prettier_store = cx.new_model(|cx| {
PrettierStore::new(
node.clone(),
fs.clone(),
languages.clone(),
worktree_store.clone(),
cx,
)
});
let settings_observer = cx.new_model(|cx| {
SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
});
@ -643,6 +648,7 @@ impl Project {
LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
prettier_store.clone(),
environment.clone(),
languages.clone(),
Some(client.http_client()),
@ -658,14 +664,10 @@ impl Project {
worktree_store,
buffer_store,
lsp_store,
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
join_project_response_message_id: 0,
client_state: ProjectClientState::Local,
client_subscriptions: Vec::new(),
_subscriptions: vec![
cx.observe_global::<SettingsStore>(Self::on_settings_changed),
cx.on_release(Self::release),
],
_subscriptions: vec![cx.on_release(Self::release)],
active_entry: None,
snippets,
languages,
@ -680,9 +682,6 @@ impl Project {
local_handles: Vec::new(),
},
node: Some(node),
default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
tasks,
hosted_project_id: None,
dev_server_project_id: None,
@ -751,14 +750,10 @@ impl Project {
worktree_store,
buffer_store,
lsp_store,
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
join_project_response_message_id: 0,
client_state: ProjectClientState::Local,
client_subscriptions: Vec::new(),
_subscriptions: vec![
cx.observe_global::<SettingsStore>(Self::on_settings_changed),
cx.on_release(Self::release),
],
_subscriptions: vec![cx.on_release(Self::release)],
active_entry: None,
snippets,
languages,
@ -773,9 +768,6 @@ impl Project {
local_handles: Vec::new(),
},
node: Some(node),
default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
tasks,
hosted_project_id: None,
dev_server_project_id: None,
@ -928,7 +920,6 @@ impl Project {
buffer_store: buffer_store.clone(),
worktree_store: worktree_store.clone(),
lsp_store: lsp_store.clone(),
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
active_entry: None,
collaborators: Default::default(),
join_project_response_message_id: response.message_id,
@ -954,9 +945,6 @@ impl Project {
local_handles: Vec::new(),
},
node: None,
default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(),
tasks,
hosted_project_id: None,
dev_server_project_id: response
@ -1176,112 +1164,6 @@ impl Project {
self.worktree_store.clone()
}
fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
let mut language_servers_to_start = Vec::new();
let mut language_formatters_to_check = Vec::new();
for buffer in self.buffer_store.read(cx).buffers() {
let buffer = buffer.read(cx);
let buffer_file = File::from_dyn(buffer.file());
let buffer_language = buffer.language();
let settings = language_settings(buffer_language, buffer.file(), cx);
if let Some(language) = buffer_language {
if settings.enable_language_server {
if let Some(file) = buffer_file {
language_servers_to_start.push((file.worktree.clone(), language.name()));
}
}
language_formatters_to_check
.push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
}
}
let mut language_servers_to_stop = Vec::new();
let mut language_servers_to_restart = Vec::new();
let languages = self.languages.to_vec();
let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
let current_lsp_settings = &self.current_lsp_settings;
for (worktree_id, started_lsp_name) in self.lsp_store.read(cx).started_language_servers() {
let language = languages.iter().find_map(|l| {
let adapter = self
.languages
.lsp_adapters(&l.name())
.iter()
.find(|adapter| adapter.name == started_lsp_name)?
.clone();
Some((l, adapter))
});
if let Some((language, adapter)) = language {
let worktree = self.worktree_for_id(worktree_id, cx);
let file = worktree.as_ref().and_then(|tree| {
tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
});
if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
} else if let Some(worktree) = worktree {
let server_name = &adapter.name.0;
match (
current_lsp_settings.get(server_name),
new_lsp_settings.get(server_name),
) {
(None, None) => {}
(Some(_), None) | (None, Some(_)) => {
language_servers_to_restart.push((worktree, language.name()));
}
(Some(current_lsp_settings), Some(new_lsp_settings)) => {
if current_lsp_settings != new_lsp_settings {
language_servers_to_restart.push((worktree, language.name()));
}
}
}
}
}
}
self.current_lsp_settings = new_lsp_settings;
// Stop all newly-disabled language servers.
self.lsp_store.update(cx, |lsp_store, cx| {
for (worktree_id, adapter_name) in language_servers_to_stop {
lsp_store
.stop_language_server(worktree_id, adapter_name, cx)
.detach();
}
});
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(&language_settings)
{
prettier_plugins_by_worktree
.entry(worktree)
.or_insert_with(HashSet::default)
.extend(plugins.iter().cloned());
}
}
for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
self.install_default_prettier(
worktree,
prettier_plugins.into_iter().map(Arc::from),
cx,
);
}
// Start all the newly-enabled language servers.
self.lsp_store.update(cx, |lsp_store, cx| {
for (worktree, language) in language_servers_to_start {
lsp_store.start_language_servers(&worktree, language, cx);
}
// Restart all language servers with changed initialization options.
for (worktree, language) in language_servers_to_restart {
lsp_store.restart_language_servers(worktree, language, cx);
}
});
cx.notify();
}
pub fn buffer_for_id(&self, remote_id: BufferId, cx: &AppContext) -> Option<Model<Buffer>> {
self.buffer_store.read(cx).get(remote_id)
}
@ -2160,24 +2042,10 @@ impl Project {
buffer,
new_language,
} => {
let Some(new_language) = new_language else {
let Some(_) = new_language else {
cx.emit(Event::LanguageNotFound(buffer.clone()));
return;
};
let buffer_file = buffer.read(cx).file().cloned();
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(&settings)
{
self.install_default_prettier(
worktree,
prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
cx,
);
};
}
LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
LspStoreEvent::LanguageServerPrompt(prompt) => {
@ -2253,7 +2121,6 @@ impl Project {
worktree::Event::UpdatedEntries(changes) => {
if is_local {
this.update_local_worktree_settings(&worktree, changes, cx);
this.update_prettier_settings(&worktree, changes, cx);
}
cx.emit(Event::WorktreeUpdatedEntries(
@ -2300,37 +2167,6 @@ impl Project {
return;
}
let mut prettier_instances_to_clean = FuturesUnordered::new();
if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
for path in prettier_paths.iter().flatten() {
if let Some(prettier_instance) = self.prettier_instances.remove(path) {
prettier_instances_to_clean.push(async move {
prettier_instance
.server()
.await
.map(|server| server.server_id())
});
}
}
}
cx.spawn(|project, mut cx| async move {
while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
if let Some(prettier_server_id) = prettier_server_id {
project
.update(&mut cx, |project, cx| {
project.lsp_store.update(cx, |lsp_store, cx| {
lsp_store.unregister_supplementary_language_server(
prettier_server_id,
cx,
);
});
})
.ok();
}
}
})
.detach();
self.task_inventory().update(cx, |inventory, _| {
inventory.remove_worktree_sources(id_to_remove);
});
@ -3059,11 +2895,21 @@ impl Project {
None
}
}
Formatter::Prettier => prettier_support::format_with_prettier(&project, buffer, cx)
.await
.transpose()
.ok()
.flatten(),
Formatter::Prettier => {
let prettier = project.update(cx, |project, cx| {
project
.lsp_store
.read(cx)
.prettier_store()
.unwrap()
.downgrade()
})?;
prettier_store::format_with_prettier(&prettier, buffer, cx)
.await
.transpose()
.ok()
.flatten()
}
Formatter::External { command, arguments } => {
let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)

View file

@ -26,6 +26,7 @@ env_logger.workspace = true
fs.workspace = true
futures.workspace = true
gpui.workspace = true
node_runtime.workspace = true
log.workspace = true
project.workspace = true
remote.workspace = true

View file

@ -2,12 +2,13 @@ use anyhow::{anyhow, Result};
use fs::Fs;
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
use node_runtime::DummyNodeRuntime;
use project::{
buffer_store::{BufferStore, BufferStoreEvent},
project_settings::SettingsObserver,
search::SearchQuery,
worktree_store::WorktreeStore,
LspStore, LspStoreEvent, ProjectPath, WorktreeId,
LspStore, LspStoreEvent, PrettierStore, ProjectPath, WorktreeId,
};
use remote::SshSession;
use rpc::{
@ -54,6 +55,16 @@ impl HeadlessProject {
buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
buffer_store
});
let prettier_store = cx.new_model(|cx| {
PrettierStore::new(
DummyNodeRuntime::new(),
fs.clone(),
languages.clone(),
worktree_store.clone(),
cx,
)
});
let settings_observer = cx.new_model(|cx| {
let mut observer = SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx);
observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);
@ -64,6 +75,7 @@ impl HeadlessProject {
let mut lsp_store = LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
prettier_store.clone(),
environment,
languages.clone(),
None,