formatting: Use project environment to find external formatters (#18611)
Closes #18261 This makes sure that we find external formatters in the project environment. TODO: - [x] Use a different type for the triplet of `(buffer_handle, buffer_path, buffer_env)`. Something like `FormattableBuffer`. - [x] Test this!! Release Notes: - Fixed external formatters not being found, even when they were available in the `$PATH` of a project. --------- Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
parent
c03b8d6c48
commit
9c5bec5efb
1 changed files with 87 additions and 48 deletions
|
@ -158,7 +158,7 @@ impl LocalLspStore {
|
|||
|
||||
async fn format_locally(
|
||||
lsp_store: WeakModel<LspStore>,
|
||||
mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
|
||||
mut buffers: Vec<FormattableBuffer>,
|
||||
push_to_history: bool,
|
||||
trigger: FormatTrigger,
|
||||
mut cx: AsyncAppContext,
|
||||
|
@ -167,22 +167,22 @@ impl LocalLspStore {
|
|||
// same buffer.
|
||||
lsp_store.update(&mut cx, |this, cx| {
|
||||
let this = this.as_local_mut().unwrap();
|
||||
buffers_with_paths.retain(|(buffer, _)| {
|
||||
buffers.retain(|buffer| {
|
||||
this.buffers_being_formatted
|
||||
.insert(buffer.read(cx).remote_id())
|
||||
.insert(buffer.handle.read(cx).remote_id())
|
||||
});
|
||||
})?;
|
||||
|
||||
let _cleanup = defer({
|
||||
let this = lsp_store.clone();
|
||||
let mut cx = cx.clone();
|
||||
let buffers = &buffers_with_paths;
|
||||
let buffers = &buffers;
|
||||
move || {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let this = this.as_local_mut().unwrap();
|
||||
for (buffer, _) in buffers {
|
||||
for buffer in buffers {
|
||||
this.buffers_being_formatted
|
||||
.remove(&buffer.read(cx).remote_id());
|
||||
.remove(&buffer.handle.read(cx).remote_id());
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
@ -190,10 +190,10 @@ impl LocalLspStore {
|
|||
});
|
||||
|
||||
let mut project_transaction = ProjectTransaction::default();
|
||||
for (buffer, buffer_abs_path) in &buffers_with_paths {
|
||||
for buffer in &buffers {
|
||||
let (primary_adapter_and_server, adapters_and_servers) =
|
||||
lsp_store.update(&mut cx, |lsp_store, cx| {
|
||||
let buffer = buffer.read(cx);
|
||||
let buffer = buffer.handle.read(cx);
|
||||
|
||||
let adapters_and_servers = lsp_store
|
||||
.language_servers_for_buffer(buffer, cx)
|
||||
|
@ -207,7 +207,7 @@ impl LocalLspStore {
|
|||
(primary_adapter, adapters_and_servers)
|
||||
})?;
|
||||
|
||||
let settings = buffer.update(&mut cx, |buffer, cx| {
|
||||
let settings = buffer.handle.update(&mut cx, |buffer, cx| {
|
||||
language_settings(buffer.language(), buffer.file(), cx).clone()
|
||||
})?;
|
||||
|
||||
|
@ -218,13 +218,14 @@ impl LocalLspStore {
|
|||
let trailing_whitespace_diff = if remove_trailing_whitespace {
|
||||
Some(
|
||||
buffer
|
||||
.handle
|
||||
.update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
|
||||
.await,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
|
||||
let whitespace_transaction_id = buffer.handle.update(&mut cx, |buffer, cx| {
|
||||
buffer.finalize_last_transaction();
|
||||
buffer.start_transaction();
|
||||
if let Some(diff) = trailing_whitespace_diff {
|
||||
|
@ -246,7 +247,7 @@ impl LocalLspStore {
|
|||
&lsp_store,
|
||||
&adapters_and_servers,
|
||||
code_actions,
|
||||
buffer,
|
||||
&buffer.handle,
|
||||
push_to_history,
|
||||
&mut project_transaction,
|
||||
&mut cx,
|
||||
|
@ -261,9 +262,9 @@ impl LocalLspStore {
|
|||
primary_adapter_and_server.map(|(_adapter, server)| server.clone());
|
||||
let server_and_buffer = primary_language_server
|
||||
.as_ref()
|
||||
.zip(buffer_abs_path.as_ref());
|
||||
.zip(buffer.abs_path.as_ref());
|
||||
|
||||
let prettier_settings = buffer.read_with(&cx, |buffer, cx| {
|
||||
let prettier_settings = buffer.handle.read_with(&cx, |buffer, cx| {
|
||||
language_settings(buffer.language(), buffer.file(), cx)
|
||||
.prettier
|
||||
.clone()
|
||||
|
@ -288,7 +289,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -302,7 +302,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -325,7 +324,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -351,7 +349,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -379,7 +376,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -393,7 +389,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -418,7 +413,6 @@ impl LocalLspStore {
|
|||
server_and_buffer,
|
||||
lsp_store.clone(),
|
||||
buffer,
|
||||
buffer_abs_path,
|
||||
&settings,
|
||||
&adapters_and_servers,
|
||||
push_to_history,
|
||||
|
@ -438,7 +432,7 @@ impl LocalLspStore {
|
|||
}
|
||||
}
|
||||
|
||||
buffer.update(&mut cx, |b, cx| {
|
||||
buffer.handle.update(&mut cx, |b, cx| {
|
||||
// If the buffer had its whitespace formatted and was edited while the language-specific
|
||||
// formatting was being computed, avoid applying the language-specific formatting, because
|
||||
// it can't be grouped with the whitespace formatting in the undo history.
|
||||
|
@ -467,7 +461,7 @@ impl LocalLspStore {
|
|||
|
||||
if let Some(transaction_id) = whitespace_transaction_id {
|
||||
b.group_until_transaction(transaction_id);
|
||||
} else if let Some(transaction) = project_transaction.0.get(buffer) {
|
||||
} else if let Some(transaction) = project_transaction.0.get(&buffer.handle) {
|
||||
b.group_until_transaction(transaction.id)
|
||||
}
|
||||
}
|
||||
|
@ -476,7 +470,9 @@ impl LocalLspStore {
|
|||
if !push_to_history {
|
||||
b.forget_transaction(transaction.id);
|
||||
}
|
||||
project_transaction.0.insert(buffer.clone(), transaction);
|
||||
project_transaction
|
||||
.0
|
||||
.insert(buffer.handle.clone(), transaction);
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
@ -489,8 +485,7 @@ impl LocalLspStore {
|
|||
formatter: &Formatter,
|
||||
primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
|
||||
lsp_store: WeakModel<LspStore>,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_abs_path: &Option<PathBuf>,
|
||||
buffer: &FormattableBuffer,
|
||||
settings: &LanguageSettings,
|
||||
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
|
||||
push_to_history: bool,
|
||||
|
@ -514,7 +509,7 @@ impl LocalLspStore {
|
|||
Some(FormatOperation::Lsp(
|
||||
LspStore::format_via_lsp(
|
||||
&lsp_store,
|
||||
buffer,
|
||||
&buffer.handle,
|
||||
buffer_abs_path,
|
||||
language_server,
|
||||
settings,
|
||||
|
@ -531,27 +526,20 @@ impl LocalLspStore {
|
|||
let prettier = lsp_store.update(cx, |lsp_store, _cx| {
|
||||
lsp_store.prettier_store().unwrap().downgrade()
|
||||
})?;
|
||||
prettier_store::format_with_prettier(&prettier, buffer, cx)
|
||||
prettier_store::format_with_prettier(&prettier, &buffer.handle, 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.as_deref(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.context(format!(
|
||||
"failed to format via external command {:?}",
|
||||
command
|
||||
))?
|
||||
.map(FormatOperation::External)
|
||||
Self::format_via_external_command(buffer, command, arguments.as_deref(), cx)
|
||||
.await
|
||||
.context(format!(
|
||||
"failed to format via external command {:?}",
|
||||
command
|
||||
))?
|
||||
.map(FormatOperation::External)
|
||||
}
|
||||
Formatter::CodeActions(code_actions) => {
|
||||
let code_actions = deserialize_code_actions(code_actions);
|
||||
|
@ -560,7 +548,7 @@ impl LocalLspStore {
|
|||
&lsp_store,
|
||||
adapters_and_servers,
|
||||
code_actions,
|
||||
buffer,
|
||||
&buffer.handle,
|
||||
push_to_history,
|
||||
transaction,
|
||||
cx,
|
||||
|
@ -574,13 +562,12 @@ impl LocalLspStore {
|
|||
}
|
||||
|
||||
async fn format_via_external_command(
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_abs_path: Option<&Path>,
|
||||
buffer: &FormattableBuffer,
|
||||
command: &str,
|
||||
arguments: Option<&[String]>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Option<Diff>> {
|
||||
let working_dir_path = buffer.update(cx, |buffer, cx| {
|
||||
let working_dir_path = buffer.handle.update(cx, |buffer, cx| {
|
||||
let file = File::from_dyn(buffer.file())?;
|
||||
let worktree = file.worktree.read(cx);
|
||||
let mut worktree_path = worktree.abs_path().to_path_buf();
|
||||
|
@ -597,13 +584,17 @@ impl LocalLspStore {
|
|||
child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
|
||||
}
|
||||
|
||||
if let Some(buffer_env) = buffer.env.as_ref() {
|
||||
child.envs(buffer_env);
|
||||
}
|
||||
|
||||
if let Some(working_dir_path) = working_dir_path {
|
||||
child.current_dir(working_dir_path);
|
||||
}
|
||||
|
||||
if let Some(arguments) = arguments {
|
||||
child.args(arguments.iter().map(|arg| {
|
||||
if let Some(buffer_abs_path) = buffer_abs_path {
|
||||
if let Some(buffer_abs_path) = buffer.abs_path.as_ref() {
|
||||
arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
|
||||
} else {
|
||||
arg.replace("{buffer_path}", "Untitled")
|
||||
|
@ -621,7 +612,9 @@ impl LocalLspStore {
|
|||
.stdin
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("failed to acquire stdin"))?;
|
||||
let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
|
||||
let text = buffer
|
||||
.handle
|
||||
.update(cx, |buffer, _| buffer.as_rope().clone())?;
|
||||
for chunk in text.chunks() {
|
||||
stdin.write_all(chunk.as_bytes()).await?;
|
||||
}
|
||||
|
@ -640,12 +633,19 @@ impl LocalLspStore {
|
|||
let stdout = String::from_utf8(output.stdout)?;
|
||||
Ok(Some(
|
||||
buffer
|
||||
.handle
|
||||
.update(cx, |buffer, cx| buffer.diff(stdout, cx))?
|
||||
.await,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FormattableBuffer {
|
||||
handle: Model<Buffer>,
|
||||
abs_path: Option<PathBuf>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
pub struct RemoteLspStore {
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
|
@ -5028,6 +5028,28 @@ impl LspStore {
|
|||
.and_then(|local| local.last_formatting_failure.as_deref())
|
||||
}
|
||||
|
||||
pub fn environment_for_buffer(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
let worktree_id = buffer.read(cx).file().map(|file| file.worktree_id(cx));
|
||||
let worktree_abs_path = worktree_id.and_then(|worktree_id| {
|
||||
self.worktree_store
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.map(|entry| entry.read(cx).abs_path().clone())
|
||||
});
|
||||
|
||||
if let Some(environment) = &self.as_local().map(|local| local.environment.clone()) {
|
||||
environment.update(cx, |env, cx| {
|
||||
env.get_environment(worktree_id, worktree_abs_path, cx)
|
||||
})
|
||||
} else {
|
||||
Task::ready(None).shared()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(
|
||||
&mut self,
|
||||
buffers: HashSet<Model<Buffer>>,
|
||||
|
@ -5042,14 +5064,31 @@ impl LspStore {
|
|||
let buffer = buffer_handle.read(cx);
|
||||
let buffer_abs_path = File::from_dyn(buffer.file())
|
||||
.and_then(|file| file.as_local().map(|f| f.abs_path(cx)));
|
||||
|
||||
(buffer_handle, buffer_abs_path)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
cx.spawn(move |lsp_store, mut cx| async move {
|
||||
let mut formattable_buffers = Vec::with_capacity(buffers_with_paths.len());
|
||||
|
||||
for (handle, abs_path) in buffers_with_paths {
|
||||
let env = lsp_store
|
||||
.update(&mut cx, |lsp_store, cx| {
|
||||
lsp_store.environment_for_buffer(&handle, cx)
|
||||
})?
|
||||
.await;
|
||||
|
||||
formattable_buffers.push(FormattableBuffer {
|
||||
handle,
|
||||
abs_path,
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
let result = LocalLspStore::format_locally(
|
||||
lsp_store.clone(),
|
||||
buffers_with_paths,
|
||||
formattable_buffers,
|
||||
push_to_history,
|
||||
trigger,
|
||||
cx.clone(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue