Detailed changes
@@ -280,7 +280,7 @@ impl ActivityIndicator {
}
// Show any formatting failure
- if let Some(failure) = self.project.read(cx).last_formatting_failure() {
+ if let Some(failure) = self.project.read(cx).last_formatting_failure(cx) {
return Some(Content {
icon: Some(
Icon::new(IconName::Warning)
@@ -28,8 +28,8 @@ use live_kit_client::MacOSDisplay;
use lsp::LanguageServerId;
use parking_lot::Mutex;
use project::{
- search::SearchQuery, search::SearchResult, DiagnosticSummary, FormatTrigger, HoverBlockKind,
- Project, ProjectPath,
+ lsp_store::FormatTrigger, search::SearchQuery, search::SearchResult, DiagnosticSummary,
+ HoverBlockKind, Project, ProjectPath,
};
use rand::prelude::*;
use serde_json::json;
@@ -122,8 +122,8 @@ use ordered_float::OrderedFloat;
use parking_lot::{Mutex, RwLock};
use project::project_settings::{GitGutterSetting, ProjectSettings};
use project::{
- CodeAction, Completion, CompletionIntent, FormatTrigger, Item, Location, Project, ProjectPath,
- ProjectTransaction, TaskSourceKind,
+ lsp_store::FormatTrigger, CodeAction, Completion, CompletionIntent, Item, Location, Project,
+ ProjectPath, ProjectTransaction, TaskSourceKind,
};
use rand::prelude::*;
use rpc::{proto::*, ErrorExt};
@@ -20,8 +20,8 @@ use language::{
};
use multi_buffer::AnchorRangeExt;
use project::{
- project_settings::ProjectSettings, search::SearchQuery, FormatTrigger, Item as _, Project,
- ProjectPath,
+ lsp_store::FormatTrigger, project_settings::ProjectSettings, search::SearchQuery, Item as _,
+ Project, ProjectPath,
};
use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
@@ -1,5 +1,6 @@
use crate::{
buffer_store::{BufferStore, BufferStoreEvent},
+ deserialize_code_actions,
environment::ProjectEnvironment,
lsp_command::{self, *},
lsp_ext_command,
@@ -19,7 +20,7 @@ use futures::{
future::{join_all, BoxFuture, Shared},
select,
stream::FuturesUnordered,
- Future, FutureExt, StreamExt,
+ AsyncWriteExt, Future, FutureExt, StreamExt,
};
use globset::{Glob, GlobSet, GlobSetBuilder};
use gpui::{
@@ -29,12 +30,13 @@ use gpui::{
use http_client::{AsyncBody, HttpClient, Request, Response, Uri};
use language::{
language_settings::{
- all_language_settings, language_settings, AllLanguageSettings, LanguageSettings,
+ all_language_settings, language_settings, AllLanguageSettings, FormatOnSave, Formatter,
+ LanguageSettings, SelectedFormatter,
},
markdown, point_to_lsp, prepare_completion_documentation,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeLabel, Diagnostic,
- DiagnosticEntry, DiagnosticSet, Documentation, File as _, Language, LanguageConfig,
+ DiagnosticEntry, DiagnosticSet, Diff, Documentation, File as _, Language, LanguageConfig,
LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerName, LocalFile, LspAdapter,
LspAdapterDelegate, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset,
ToPointUtf16, Transaction, Unclipped,
@@ -90,12 +92,38 @@ const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
const SERVER_LAUNCHING_BEFORE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
pub const SERVER_PROGRESS_THROTTLE_TIMEOUT: Duration = Duration::from_millis(100);
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FormatTrigger {
+ Save,
+ Manual,
+}
+
+// Currently, formatting operations are represented differently depending on
+// whether they come from a language server or an external command.
+#[derive(Debug)]
+pub enum FormatOperation {
+ Lsp(Vec<(Range<Anchor>, String)>),
+ External(Diff),
+ Prettier(Diff),
+}
+
+impl FormatTrigger {
+ fn from_proto(value: i32) -> FormatTrigger {
+ match value {
+ 0 => FormatTrigger::Save,
+ 1 => FormatTrigger::Manual,
+ _ => FormatTrigger::Save,
+ }
+ }
+}
+
pub struct LocalLspStore {
http_client: Option<Arc<dyn HttpClient>>,
environment: Model<ProjectEnvironment>,
fs: Arc<dyn Fs>,
yarn: Model<YarnPathStore>,
pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
+ buffers_being_formatted: HashSet<BufferId>,
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
language_server_watcher_registrations:
@@ -104,6 +132,7 @@ pub struct LocalLspStore {
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
prettier_store: Model<PrettierStore>,
current_lsp_settings: HashMap<LanguageServerName, LspSettings>,
+ last_formatting_failure: Option<String>,
_subscription: gpui::Subscription,
}
@@ -128,6 +157,485 @@ impl LocalLspStore {
futures::future::join_all(shutdown_futures).await;
}
}
+ async fn format_locally(
+ lsp_store: WeakModel<LspStore>,
+ mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
+ push_to_history: bool,
+ trigger: FormatTrigger,
+ mut cx: AsyncAppContext,
+ ) -> anyhow::Result<ProjectTransaction> {
+ // Do not allow multiple concurrent formatting requests for the
+ // same buffer.
+ lsp_store.update(&mut cx, |this, cx| {
+ let this = this.as_local_mut().unwrap();
+ buffers_with_paths.retain(|(buffer, _)| {
+ this.buffers_being_formatted
+ .insert(buffer.read(cx).remote_id())
+ });
+ })?;
+
+ let _cleanup = defer({
+ let this = lsp_store.clone();
+ let mut cx = cx.clone();
+ let buffers = &buffers_with_paths;
+ move || {
+ this.update(&mut cx, |this, cx| {
+ let this = this.as_local_mut().unwrap();
+ for (buffer, _) in buffers {
+ this.buffers_being_formatted
+ .remove(&buffer.read(cx).remote_id());
+ }
+ })
+ .ok();
+ }
+ });
+
+ let mut project_transaction = ProjectTransaction::default();
+ for (buffer, buffer_abs_path) in &buffers_with_paths {
+ let (primary_adapter_and_server, adapters_and_servers) =
+ lsp_store.update(&mut cx, |lsp_store, cx| {
+ let buffer = buffer.read(cx);
+
+ let adapters_and_servers = lsp_store
+ .language_servers_for_buffer(buffer, cx)
+ .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
+ .collect::<Vec<_>>();
+
+ let primary_adapter = lsp_store
+ .primary_language_server_for_buffer(buffer, cx)
+ .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()));
+
+ (primary_adapter, adapters_and_servers)
+ })?;
+
+ let settings = buffer.update(&mut cx, |buffer, cx| {
+ language_settings(buffer.language(), buffer.file(), cx).clone()
+ })?;
+
+ let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
+ let ensure_final_newline = settings.ensure_final_newline_on_save;
+
+ // First, format buffer's whitespace according to the settings.
+ let trailing_whitespace_diff = if remove_trailing_whitespace {
+ Some(
+ buffer
+ .update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
+ .await,
+ )
+ } else {
+ None
+ };
+ let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
+ buffer.finalize_last_transaction();
+ buffer.start_transaction();
+ if let Some(diff) = trailing_whitespace_diff {
+ buffer.apply_diff(diff, cx);
+ }
+ if ensure_final_newline {
+ buffer.ensure_final_newline(cx);
+ }
+ buffer.end_transaction(cx)
+ })?;
+
+ // Apply the `code_actions_on_format` before we run the formatter.
+ let code_actions = deserialize_code_actions(&settings.code_actions_on_format);
+ #[allow(clippy::nonminimal_bool)]
+ if !code_actions.is_empty()
+ && !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off)
+ {
+ LspStore::execute_code_actions_on_servers(
+ &lsp_store,
+ &adapters_and_servers,
+ code_actions,
+ buffer,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await?;
+ }
+
+ // Apply language-specific formatting using either the primary language server
+ // or external command.
+ // Except for code actions, which are applied with all connected language servers.
+ let primary_language_server =
+ primary_adapter_and_server.map(|(_adapter, server)| server.clone());
+ let server_and_buffer = primary_language_server
+ .as_ref()
+ .zip(buffer_abs_path.as_ref());
+
+ let prettier_settings = buffer.read_with(&cx, |buffer, cx| {
+ language_settings(buffer.language(), buffer.file(), cx)
+ .prettier
+ .clone()
+ })?;
+
+ let mut format_operations: Vec<FormatOperation> = vec![];
+ {
+ match trigger {
+ FormatTrigger::Save => {
+ match &settings.format_on_save {
+ FormatOnSave::Off => {
+ // nothing
+ }
+ FormatOnSave::On => {
+ match &settings.formatter {
+ SelectedFormatter::Auto => {
+ // do the auto-format: prefer prettier, fallback to primary language server
+ let diff = {
+ if prettier_settings.allowed {
+ Self::perform_format(
+ &Formatter::Prettier,
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ } else {
+ Self::perform_format(
+ &Formatter::LanguageServer { name: None },
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ }
+ }
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+ }
+ SelectedFormatter::List(formatters) => {
+ for formatter in formatters.as_ref() {
+ let diff = Self::perform_format(
+ formatter,
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+
+ // format with formatter
+ }
+ }
+ }
+ }
+ FormatOnSave::List(formatters) => {
+ for formatter in formatters.as_ref() {
+ let diff = Self::perform_format(
+ formatter,
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+ }
+ }
+ }
+ }
+ FormatTrigger::Manual => {
+ match &settings.formatter {
+ SelectedFormatter::Auto => {
+ // do the auto-format: prefer prettier, fallback to primary language server
+ let diff = {
+ if prettier_settings.allowed {
+ Self::perform_format(
+ &Formatter::Prettier,
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ } else {
+ Self::perform_format(
+ &Formatter::LanguageServer { name: None },
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ }
+ }
+ .log_err()
+ .flatten();
+
+ if let Some(op) = diff {
+ format_operations.push(op)
+ }
+ }
+ SelectedFormatter::List(formatters) => {
+ for formatter in formatters.as_ref() {
+ // format with formatter
+ let diff = Self::perform_format(
+ formatter,
+ server_and_buffer,
+ lsp_store.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ buffer.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.
+ if let Some(transaction_id) = whitespace_transaction_id {
+ if b.peek_undo_stack()
+ .map_or(true, |e| e.transaction_id() != transaction_id)
+ {
+ format_operations.clear();
+ }
+ }
+
+ // Apply any language-specific formatting, and group the two formatting operations
+ // in the buffer's undo history.
+ for operation in format_operations {
+ match operation {
+ FormatOperation::Lsp(edits) => {
+ b.edit(edits, None, cx);
+ }
+ FormatOperation::External(diff) => {
+ b.apply_diff(diff, cx);
+ }
+ FormatOperation::Prettier(diff) => {
+ b.apply_diff(diff, cx);
+ }
+ }
+
+ if let Some(transaction_id) = whitespace_transaction_id {
+ b.group_until_transaction(transaction_id);
+ } else if let Some(transaction) = project_transaction.0.get(buffer) {
+ b.group_until_transaction(transaction.id)
+ }
+ }
+
+ if let Some(transaction) = b.finalize_last_transaction().cloned() {
+ if !push_to_history {
+ b.forget_transaction(transaction.id);
+ }
+ project_transaction.0.insert(buffer.clone(), transaction);
+ }
+ })?;
+ }
+
+ Ok(project_transaction)
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ async fn perform_format(
+ formatter: &Formatter,
+ primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
+ lsp_store: WeakModel<LspStore>,
+ buffer: &Model<Buffer>,
+ buffer_abs_path: &Option<PathBuf>,
+ settings: &LanguageSettings,
+ adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
+ push_to_history: bool,
+ transaction: &mut ProjectTransaction,
+ cx: &mut AsyncAppContext,
+ ) -> Result<Option<FormatOperation>, anyhow::Error> {
+ let result = match formatter {
+ Formatter::LanguageServer { name } => {
+ if let Some((language_server, buffer_abs_path)) = primary_server_and_buffer {
+ let language_server = if let Some(name) = name {
+ adapters_and_servers
+ .iter()
+ .find_map(|(adapter, server)| {
+ adapter.name.0.as_ref().eq(name.as_str()).then_some(server)
+ })
+ .unwrap_or(language_server)
+ } else {
+ language_server
+ };
+
+ Some(FormatOperation::Lsp(
+ LspStore::format_via_lsp(
+ &lsp_store,
+ buffer,
+ buffer_abs_path,
+ language_server,
+ settings,
+ cx,
+ )
+ .await
+ .context("failed to format via language server")?,
+ ))
+ } else {
+ None
+ }
+ }
+ Formatter::Prettier => {
+ let prettier = lsp_store.update(cx, |lsp_store, _cx| {
+ lsp_store.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)
+ .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);
+ if !code_actions.is_empty() {
+ LspStore::execute_code_actions_on_servers(
+ &lsp_store,
+ adapters_and_servers,
+ code_actions,
+ buffer,
+ push_to_history,
+ transaction,
+ cx,
+ )
+ .await?;
+ }
+ None
+ }
+ };
+ anyhow::Ok(result)
+ }
+
+ async fn format_via_external_command(
+ buffer: &Model<Buffer>,
+ buffer_abs_path: Option<&Path>,
+ command: &str,
+ arguments: &[String],
+ cx: &mut AsyncAppContext,
+ ) -> Result<Option<Diff>> {
+ let working_dir_path = buffer.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();
+ if worktree.root_entry()?.is_file() {
+ worktree_path.pop();
+ }
+ Some(worktree_path)
+ })?;
+
+ let mut child = smol::process::Command::new(command);
+ #[cfg(target_os = "windows")]
+ {
+ use smol::process::windows::CommandExt;
+ child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
+ }
+
+ if let Some(working_dir_path) = working_dir_path {
+ child.current_dir(working_dir_path);
+ }
+
+ let mut child = child
+ .args(arguments.iter().map(|arg| {
+ if let Some(buffer_abs_path) = buffer_abs_path {
+ arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
+ } else {
+ arg.replace("{buffer_path}", "Untitled")
+ }
+ }))
+ .stdin(smol::process::Stdio::piped())
+ .stdout(smol::process::Stdio::piped())
+ .stderr(smol::process::Stdio::piped())
+ .spawn()?;
+
+ let stdin = child
+ .stdin
+ .as_mut()
+ .ok_or_else(|| anyhow!("failed to acquire stdin"))?;
+ let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
+ for chunk in text.chunks() {
+ stdin.write_all(chunk.as_bytes()).await?;
+ }
+ stdin.flush().await?;
+
+ let output = child.output().await?;
+ if !output.status.success() {
+ return Err(anyhow!(
+ "command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
+ output.status.code(),
+ String::from_utf8_lossy(&output.stdout),
+ String::from_utf8_lossy(&output.stderr),
+ ));
+ }
+
+ let stdout = String::from_utf8(output.stdout)?;
+ Ok(Some(
+ buffer
+ .update(cx, |buffer, cx| buffer.diff(stdout, cx))?
+ .await,
+ ))
+ }
}
pub struct RemoteLspStore {
@@ -221,8 +729,6 @@ pub enum LspStoreEvent {
edits: Vec<(lsp::Range, Snippet)>,
most_recent_edit: clock::Lamport,
},
- StartFormattingLocalBuffer(BufferId),
- FinishFormattingLocalBuffer(BufferId),
}
#[derive(Clone, Debug, Serialize)]
@@ -251,6 +757,7 @@ impl LspStore {
client.add_model_message_handler(Self::handle_start_language_server);
client.add_model_message_handler(Self::handle_update_language_server);
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
+ client.add_model_request_handler(Self::handle_format_buffers);
client.add_model_request_handler(Self::handle_resolve_completion_documentation);
client.add_model_request_handler(Self::handle_apply_code_action);
client.add_model_request_handler(Self::handle_inlay_hints);
@@ -366,6 +873,8 @@ impl LspStore {
language_server_watched_paths: Default::default(),
language_server_watcher_registrations: Default::default(),
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
+ buffers_being_formatted: Default::default(),
+ last_formatting_failure: None,
prettier_store,
environment,
http_client,
@@ -387,6 +896,7 @@ impl LspStore {
diagnostic_summaries: Default::default(),
diagnostics: Default::default(),
active_entry: None,
+
_maintain_workspace_config: Self::maintain_workspace_config(cx),
_maintain_buffer_languages: Self::maintain_buffer_languages(languages.clone(), cx),
}
@@ -1276,7 +1786,7 @@ impl LspStore {
}
fn apply_on_type_formatting(
- &self,
+ &mut self,
buffer: Model<Buffer>,
position: Anchor,
trigger: String,
@@ -1298,25 +1808,18 @@ impl LspStore {
.map(language::proto::deserialize_transaction)
.transpose()
})
- } else {
+ } else if let Some(local) = self.as_local_mut() {
+ let buffer_id = buffer.read(cx).remote_id();
+ local.buffers_being_formatted.insert(buffer_id);
cx.spawn(move |this, mut cx| async move {
- // Do not allow multiple concurrent formatting requests for the
- // same buffer.
- this.update(&mut cx, |_, cx| {
- cx.emit(LspStoreEvent::StartFormattingLocalBuffer(
- buffer.read(cx).remote_id(),
- ));
- })?;
-
let _cleanup = defer({
let this = this.clone();
let mut cx = cx.clone();
- let closure_buffer = buffer.clone();
move || {
- this.update(&mut cx, |_, cx| {
- cx.emit(LspStoreEvent::FinishFormattingLocalBuffer(
- closure_buffer.read(cx).remote_id(),
- ))
+ this.update(&mut cx, |this, _| {
+ if let Some(local) = this.as_local_mut() {
+ local.buffers_being_formatted.remove(&buffer_id);
+ }
})
.ok();
}
@@ -1333,6 +1836,8 @@ impl LspStore {
})?
.await
})
+ } else {
+ Task::ready(Err(anyhow!("No upstream client or local language server")))
}
}
@@ -4708,6 +5213,110 @@ impl LspStore {
.map(language::proto::serialize_transaction),
})
}
+ pub fn last_formatting_failure(&self) -> Option<&str> {
+ self.as_local()
+ .and_then(|local| local.last_formatting_failure.as_deref())
+ }
+
+ pub fn format(
+ &mut self,
+ buffers: HashSet<Model<Buffer>>,
+ push_to_history: bool,
+ trigger: FormatTrigger,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<anyhow::Result<ProjectTransaction>> {
+ if let Some(_) = self.as_local() {
+ let buffers_with_paths = buffers
+ .into_iter()
+ .map(|buffer_handle| {
+ 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 result = LocalLspStore::format_locally(
+ lsp_store.clone(),
+ buffers_with_paths,
+ push_to_history,
+ trigger,
+ cx.clone(),
+ )
+ .await;
+
+ lsp_store.update(&mut cx, |lsp_store, _| {
+ let local = lsp_store.as_local_mut().unwrap();
+ match &result {
+ Ok(_) => local.last_formatting_failure = None,
+ Err(error) => {
+ local.last_formatting_failure.replace(error.to_string());
+ }
+ }
+ })?;
+
+ result
+ })
+ } else if let Some((client, project_id)) = self.upstream_client() {
+ cx.spawn(move |this, mut cx| async move {
+ let response = client
+ .request(proto::FormatBuffers {
+ project_id,
+ trigger: trigger as i32,
+ buffer_ids: buffers
+ .iter()
+ .map(|buffer| {
+ buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
+ })
+ .collect::<Result<_>>()?,
+ })
+ .await?
+ .transaction
+ .ok_or_else(|| anyhow!("missing transaction"))?;
+ BufferStore::deserialize_project_transaction(
+ this.read_with(&cx, |this, _| this.buffer_store.downgrade())?,
+ response,
+ push_to_history,
+ cx,
+ )
+ .await
+ })
+ } else {
+ Task::ready(Ok(ProjectTransaction::default()))
+ }
+ }
+
+ async fn handle_format_buffers(
+ this: Model<Self>,
+ envelope: TypedEnvelope<proto::FormatBuffers>,
+ mut cx: AsyncAppContext,
+ ) -> Result<proto::FormatBuffersResponse> {
+ let sender_id = envelope.original_sender_id().unwrap_or_default();
+ let format = this.update(&mut cx, |this, cx| {
+ let mut buffers = HashSet::default();
+ for buffer_id in &envelope.payload.buffer_ids {
+ let buffer_id = BufferId::new(*buffer_id)?;
+ buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
+ }
+ let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
+ Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
+ })??;
+
+ let project_transaction = format.await?;
+ let project_transaction = this.update(&mut cx, |this, cx| {
+ this.buffer_store.update(cx, |buffer_store, cx| {
+ buffer_store.serialize_project_transaction_for_peer(
+ project_transaction,
+ sender_id,
+ cx,
+ )
+ })
+ })?;
+ Ok(proto::FormatBuffersResponse {
+ transaction: Some(project_transaction),
+ })
+ }
fn language_settings<'a>(
&'a self,
@@ -25,8 +25,8 @@ use smol::stream::StreamExt;
use util::{ResultExt, TryFutureExt};
use crate::{
- worktree_store::WorktreeStore, File, FormatOperation, PathChange, ProjectEntryId, Worktree,
- WorktreeId,
+ lsp_store::WorktreeId, worktree_store::WorktreeStore, File, PathChange, ProjectEntryId,
+ Worktree,
};
pub struct PrettierStore {
@@ -644,7 +644,7 @@ pub(super) async fn format_with_prettier(
prettier_store: &WeakModel<PrettierStore>,
buffer: &Model<Buffer>,
cx: &mut AsyncAppContext,
-) -> Option<Result<FormatOperation>> {
+) -> Option<Result<crate::lsp_store::FormatOperation>> {
let prettier_instance = prettier_store
.update(cx, |prettier_store, cx| {
prettier_store.prettier_instance_for_buffer(buffer, cx)
@@ -671,7 +671,7 @@ pub(super) async fn format_with_prettier(
let format_result = prettier
.format(buffer, buffer_path, cx)
.await
- .map(FormatOperation::Prettier)
+ .map(crate::lsp_store::FormatOperation::Prettier)
.with_context(|| format!("{} failed to format buffer", prettier_description));
Some(format_result)
@@ -31,7 +31,7 @@ pub use environment::ProjectEnvironment;
use futures::{
channel::mpsc::{self, UnboundedReceiver},
future::try_join_all,
- AsyncWriteExt, StreamExt,
+ StreamExt,
};
use git::{blame::Blame, repository::GitRepository};
@@ -41,17 +41,14 @@ use gpui::{
};
use itertools::Itertools;
use language::{
- language_settings::{
- language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings,
- SelectedFormatter,
- },
+ language_settings::InlayHintKind,
proto::{
deserialize_anchor, serialize_anchor, serialize_line_ending, serialize_version,
split_operations,
},
Buffer, BufferEvent, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry,
- Diff, Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16,
- ToOffset, ToPointUtf16, Transaction, Unclipped,
+ Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset,
+ ToPointUtf16, Transaction, Unclipped,
};
use lsp::{CompletionContext, DocumentHighlightKind, LanguageServer, LanguageServerId};
use lsp_command::*;
@@ -84,7 +81,7 @@ use task::{
};
use terminals::Terminals;
use text::{Anchor, BufferId};
-use util::{defer, paths::compare_paths, ResultExt as _};
+use util::{paths::compare_paths, ResultExt as _};
use worktree::{CreatedEntry, Snapshot, Traversal};
use worktree_store::{WorktreeStore, WorktreeStoreEvent};
@@ -164,8 +161,6 @@ pub struct Project {
search_included_history: SearchHistory,
search_excluded_history: SearchHistory,
snippets: Model<SnippetProvider>,
- last_formatting_failure: Option<String>,
- buffers_being_formatted: HashSet<BufferId>,
environment: Model<ProjectEnvironment>,
settings_observer: Model<SettingsObserver>,
}
@@ -477,31 +472,6 @@ impl Hover {
}
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum FormatTrigger {
- Save,
- Manual,
-}
-
-// Currently, formatting operations are represented differently depending on
-// whether they come from a language server or an external command.
-#[derive(Debug)]
-enum FormatOperation {
- Lsp(Vec<(Range<Anchor>, String)>),
- External(Diff),
- Prettier(Diff),
-}
-
-impl FormatTrigger {
- fn from_proto(value: i32) -> FormatTrigger {
- match value {
- 0 => FormatTrigger::Save,
- 1 => FormatTrigger::Manual,
- _ => FormatTrigger::Save,
- }
- }
-}
-
enum EntitySubscription {
Project(PendingEntitySubscription<Project>),
BufferStore(PendingEntitySubscription<BufferStore>),
@@ -591,7 +561,7 @@ impl Project {
client.add_model_message_handler(Self::handle_update_worktree);
client.add_model_request_handler(Self::handle_reload_buffers);
client.add_model_request_handler(Self::handle_synchronize_buffers);
- client.add_model_request_handler(Self::handle_format_buffers);
+
client.add_model_request_handler(Self::handle_search_project);
client.add_model_request_handler(Self::handle_search_candidate_buffers);
client.add_model_request_handler(Self::handle_open_buffer_by_id);
@@ -695,8 +665,7 @@ impl Project {
search_history: Self::new_search_history(),
environment,
remotely_created_models: Default::default(),
- last_formatting_failure: None,
- buffers_being_formatted: Default::default(),
+
search_included_history: Self::new_search_history(),
search_excluded_history: Self::new_search_history(),
}
@@ -779,8 +748,7 @@ impl Project {
search_history: Self::new_search_history(),
environment,
remotely_created_models: Default::default(),
- last_formatting_failure: None,
- buffers_being_formatted: Default::default(),
+
search_included_history: Self::new_search_history(),
search_excluded_history: Self::new_search_history(),
};
@@ -967,8 +935,6 @@ impl Project {
search_excluded_history: Self::new_search_history(),
environment: ProjectEnvironment::new(&worktree_store, None, cx),
remotely_created_models: Arc::new(Mutex::new(RemotelyCreatedModels::default())),
- last_formatting_failure: None,
- buffers_being_formatted: Default::default(),
};
this.set_role(role, cx);
for worktree in worktrees {
@@ -2061,12 +2027,6 @@ impl Project {
cx.emit(Event::SnippetEdit(*buffer_id, edits.clone()))
}
}
- LspStoreEvent::StartFormattingLocalBuffer(buffer_id) => {
- self.buffers_being_formatted.insert(*buffer_id);
- }
- LspStoreEvent::FinishFormattingLocalBuffer(buffer_id) => {
- self.buffers_being_formatted.remove(buffer_id);
- }
}
}
@@ -2352,8 +2312,8 @@ impl Project {
self.lsp_store.read(cx).language_server_statuses()
}
- pub fn last_formatting_failure(&self) -> Option<&str> {
- self.last_formatting_failure.as_deref()
+ pub fn last_formatting_failure<'a>(&self, cx: &'a AppContext) -> Option<&'a str> {
+ self.lsp_store.read(cx).last_formatting_failure()
}
pub fn update_diagnostics(
@@ -2455,558 +2415,12 @@ impl Project {
&mut self,
buffers: HashSet<Model<Buffer>>,
push_to_history: bool,
- trigger: FormatTrigger,
+ trigger: lsp_store::FormatTrigger,
cx: &mut ModelContext<Project>,
) -> Task<anyhow::Result<ProjectTransaction>> {
- if self.is_local_or_ssh() {
- let buffers_with_paths = buffers
- .into_iter()
- .map(|buffer_handle| {
- 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 |project, mut cx| async move {
- let result = Self::format_locally(
- project.clone(),
- buffers_with_paths,
- push_to_history,
- trigger,
- cx.clone(),
- )
- .await;
-
- project.update(&mut cx, |project, _| match &result {
- Ok(_) => project.last_formatting_failure = None,
- Err(error) => {
- project.last_formatting_failure.replace(error.to_string());
- }
- })?;
-
- result
- })
- } else {
- let remote_id = self.remote_id();
- let client = self.client.clone();
- cx.spawn(move |this, mut cx| async move {
- if let Some(project_id) = remote_id {
- let response = client
- .request(proto::FormatBuffers {
- project_id,
- trigger: trigger as i32,
- buffer_ids: buffers
- .iter()
- .map(|buffer| {
- buffer.update(&mut cx, |buffer, _| buffer.remote_id().into())
- })
- .collect::<Result<_>>()?,
- })
- .await?
- .transaction
- .ok_or_else(|| anyhow!("missing transaction"))?;
- BufferStore::deserialize_project_transaction(
- this.read_with(&cx, |this, _| this.buffer_store.downgrade())?,
- response,
- push_to_history,
- cx,
- )
- .await
- } else {
- Ok(ProjectTransaction::default())
- }
- })
- }
- }
-
- async fn format_locally(
- project: WeakModel<Project>,
- mut buffers_with_paths: Vec<(Model<Buffer>, Option<PathBuf>)>,
- push_to_history: bool,
- trigger: FormatTrigger,
- mut cx: AsyncAppContext,
- ) -> anyhow::Result<ProjectTransaction> {
- // Do not allow multiple concurrent formatting requests for the
- // same buffer.
- let lsp_store = project.update(&mut cx, |this, cx| {
- buffers_with_paths.retain(|(buffer, _)| {
- this.buffers_being_formatted
- .insert(buffer.read(cx).remote_id())
- });
- this.lsp_store.downgrade()
- })?;
-
- let _cleanup = defer({
- let this = project.clone();
- let mut cx = cx.clone();
- let buffers = &buffers_with_paths;
- move || {
- this.update(&mut cx, |this, cx| {
- for (buffer, _) in buffers {
- this.buffers_being_formatted
- .remove(&buffer.read(cx).remote_id());
- }
- })
- .ok();
- }
- });
-
- let mut project_transaction = ProjectTransaction::default();
- for (buffer, buffer_abs_path) in &buffers_with_paths {
- let (primary_adapter_and_server, adapters_and_servers) =
- project.update(&mut cx, |project, cx| {
- let buffer = buffer.read(cx);
-
- let adapters_and_servers = project
- .language_servers_for_buffer(buffer, cx)
- .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()))
- .collect::<Vec<_>>();
-
- let primary_adapter = project
- .lsp_store
- .read(cx)
- .primary_language_server_for_buffer(buffer, cx)
- .map(|(adapter, lsp)| (adapter.clone(), lsp.clone()));
-
- (primary_adapter, adapters_and_servers)
- })?;
-
- let settings = buffer.update(&mut cx, |buffer, cx| {
- language_settings(buffer.language(), buffer.file(), cx).clone()
- })?;
-
- let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
- let ensure_final_newline = settings.ensure_final_newline_on_save;
-
- // First, format buffer's whitespace according to the settings.
- let trailing_whitespace_diff = if remove_trailing_whitespace {
- Some(
- buffer
- .update(&mut cx, |b, cx| b.remove_trailing_whitespace(cx))?
- .await,
- )
- } else {
- None
- };
- let whitespace_transaction_id = buffer.update(&mut cx, |buffer, cx| {
- buffer.finalize_last_transaction();
- buffer.start_transaction();
- if let Some(diff) = trailing_whitespace_diff {
- buffer.apply_diff(diff, cx);
- }
- if ensure_final_newline {
- buffer.ensure_final_newline(cx);
- }
- buffer.end_transaction(cx)
- })?;
-
- // Apply the `code_actions_on_format` before we run the formatter.
- let code_actions = deserialize_code_actions(&settings.code_actions_on_format);
- #[allow(clippy::nonminimal_bool)]
- if !code_actions.is_empty()
- && !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off)
- {
- LspStore::execute_code_actions_on_servers(
- &lsp_store,
- &adapters_and_servers,
- code_actions,
- buffer,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await?;
- }
-
- // Apply language-specific formatting using either the primary language server
- // or external command.
- // Except for code actions, which are applied with all connected language servers.
- let primary_language_server =
- primary_adapter_and_server.map(|(_adapter, server)| server.clone());
- let server_and_buffer = primary_language_server
- .as_ref()
- .zip(buffer_abs_path.as_ref());
-
- let prettier_settings = buffer.read_with(&cx, |buffer, cx| {
- language_settings(buffer.language(), buffer.file(), cx)
- .prettier
- .clone()
- })?;
-
- let mut format_operations: Vec<FormatOperation> = vec![];
- {
- match trigger {
- FormatTrigger::Save => {
- match &settings.format_on_save {
- FormatOnSave::Off => {
- // nothing
- }
- FormatOnSave::On => {
- match &settings.formatter {
- SelectedFormatter::Auto => {
- // do the auto-format: prefer prettier, fallback to primary language server
- let diff = {
- if prettier_settings.allowed {
- Self::perform_format(
- &Formatter::Prettier,
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- } else {
- Self::perform_format(
- &Formatter::LanguageServer { name: None },
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- }
- }
- .log_err()
- .flatten();
- if let Some(op) = diff {
- format_operations.push(op);
- }
- }
- SelectedFormatter::List(formatters) => {
- for formatter in formatters.as_ref() {
- let diff = Self::perform_format(
- formatter,
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- .log_err()
- .flatten();
- if let Some(op) = diff {
- format_operations.push(op);
- }
-
- // format with formatter
- }
- }
- }
- }
- FormatOnSave::List(formatters) => {
- for formatter in formatters.as_ref() {
- let diff = Self::perform_format(
- formatter,
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- .log_err()
- .flatten();
- if let Some(op) = diff {
- format_operations.push(op);
- }
- }
- }
- }
- }
- FormatTrigger::Manual => {
- match &settings.formatter {
- SelectedFormatter::Auto => {
- // do the auto-format: prefer prettier, fallback to primary language server
- let diff = {
- if prettier_settings.allowed {
- Self::perform_format(
- &Formatter::Prettier,
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- } else {
- Self::perform_format(
- &Formatter::LanguageServer { name: None },
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- }
- }
- .log_err()
- .flatten();
-
- if let Some(op) = diff {
- format_operations.push(op)
- }
- }
- SelectedFormatter::List(formatters) => {
- for formatter in formatters.as_ref() {
- // format with formatter
- let diff = Self::perform_format(
- formatter,
- server_and_buffer,
- project.clone(),
- buffer,
- buffer_abs_path,
- &settings,
- &adapters_and_servers,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await
- .log_err()
- .flatten();
- if let Some(op) = diff {
- format_operations.push(op);
- }
- }
- }
- }
- }
- }
- }
-
- buffer.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.
- if let Some(transaction_id) = whitespace_transaction_id {
- if b.peek_undo_stack()
- .map_or(true, |e| e.transaction_id() != transaction_id)
- {
- format_operations.clear();
- }
- }
-
- // Apply any language-specific formatting, and group the two formatting operations
- // in the buffer's undo history.
- for operation in format_operations {
- match operation {
- FormatOperation::Lsp(edits) => {
- b.edit(edits, None, cx);
- }
- FormatOperation::External(diff) => {
- b.apply_diff(diff, cx);
- }
- FormatOperation::Prettier(diff) => {
- b.apply_diff(diff, cx);
- }
- }
-
- if let Some(transaction_id) = whitespace_transaction_id {
- b.group_until_transaction(transaction_id);
- } else if let Some(transaction) = project_transaction.0.get(buffer) {
- b.group_until_transaction(transaction.id)
- }
- }
-
- if let Some(transaction) = b.finalize_last_transaction().cloned() {
- if !push_to_history {
- b.forget_transaction(transaction.id);
- }
- project_transaction.0.insert(buffer.clone(), transaction);
- }
- })?;
- }
-
- Ok(project_transaction)
- }
-
- #[allow(clippy::too_many_arguments)]
- async fn perform_format(
- formatter: &Formatter,
- primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
- project: WeakModel<Project>,
- buffer: &Model<Buffer>,
- buffer_abs_path: &Option<PathBuf>,
- settings: &LanguageSettings,
- adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
- push_to_history: bool,
- transaction: &mut ProjectTransaction,
- cx: &mut AsyncAppContext,
- ) -> Result<Option<FormatOperation>, anyhow::Error> {
- let result = match formatter {
- Formatter::LanguageServer { name } => {
- if let Some((language_server, buffer_abs_path)) = primary_server_and_buffer {
- let language_server = if let Some(name) = name {
- adapters_and_servers
- .iter()
- .find_map(|(adapter, server)| {
- adapter.name.0.as_ref().eq(name.as_str()).then_some(server)
- })
- .unwrap_or(language_server)
- } else {
- language_server
- };
-
- let lsp_store = project.update(cx, |p, _| p.lsp_store.downgrade())?;
- Some(FormatOperation::Lsp(
- LspStore::format_via_lsp(
- &lsp_store,
- buffer,
- buffer_abs_path,
- language_server,
- settings,
- cx,
- )
- .await
- .context("failed to format via language server")?,
- ))
- } else {
- None
- }
- }
- 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)
- .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);
- let lsp_store = project.update(cx, |p, _| p.lsp_store.downgrade())?;
- if !code_actions.is_empty() {
- LspStore::execute_code_actions_on_servers(
- &lsp_store,
- adapters_and_servers,
- code_actions,
- buffer,
- push_to_history,
- transaction,
- cx,
- )
- .await?;
- }
- None
- }
- };
- anyhow::Ok(result)
- }
-
- async fn format_via_external_command(
- buffer: &Model<Buffer>,
- buffer_abs_path: Option<&Path>,
- command: &str,
- arguments: &[String],
- cx: &mut AsyncAppContext,
- ) -> Result<Option<Diff>> {
- let working_dir_path = buffer.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();
- if worktree.root_entry()?.is_file() {
- worktree_path.pop();
- }
- Some(worktree_path)
- })?;
-
- let mut child = smol::process::Command::new(command);
- #[cfg(target_os = "windows")]
- {
- use smol::process::windows::CommandExt;
- child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0);
- }
-
- if let Some(working_dir_path) = working_dir_path {
- child.current_dir(working_dir_path);
- }
-
- let mut child = child
- .args(arguments.iter().map(|arg| {
- if let Some(buffer_abs_path) = buffer_abs_path {
- arg.replace("{buffer_path}", &buffer_abs_path.to_string_lossy())
- } else {
- arg.replace("{buffer_path}", "Untitled")
- }
- }))
- .stdin(smol::process::Stdio::piped())
- .stdout(smol::process::Stdio::piped())
- .stderr(smol::process::Stdio::piped())
- .spawn()?;
-
- let stdin = child
- .stdin
- .as_mut()
- .ok_or_else(|| anyhow!("failed to acquire stdin"))?;
- let text = buffer.update(cx, |buffer, _| buffer.as_rope().clone())?;
- for chunk in text.chunks() {
- stdin.write_all(chunk.as_bytes()).await?;
- }
- stdin.flush().await?;
-
- let output = child.output().await?;
- if !output.status.success() {
- return Err(anyhow!(
- "command failed with exit code {:?}:\nstdout: {}\nstderr: {}",
- output.status.code(),
- String::from_utf8_lossy(&output.stdout),
- String::from_utf8_lossy(&output.stderr),
- ));
- }
-
- let stdout = String::from_utf8(output.stdout)?;
- Ok(Some(
- buffer
- .update(cx, |buffer, cx| buffer.diff(stdout, cx))?
- .await,
- ))
+ self.lsp_store.update(cx, |lsp_store, cx| {
+ lsp_store.format(buffers, push_to_history, trigger, cx)
+ })
}
#[inline(never)]
@@ -4210,31 +3624,6 @@ impl Project {
Ok(response)
}
- async fn handle_format_buffers(
- this: Model<Self>,
- envelope: TypedEnvelope<proto::FormatBuffers>,
- mut cx: AsyncAppContext,
- ) -> Result<proto::FormatBuffersResponse> {
- let sender_id = envelope.original_sender_id()?;
- let format = this.update(&mut cx, |this, cx| {
- let mut buffers = HashSet::default();
- for buffer_id in &envelope.payload.buffer_ids {
- let buffer_id = BufferId::new(*buffer_id)?;
- buffers.insert(this.buffer_store.read(cx).get_existing(buffer_id)?);
- }
- let trigger = FormatTrigger::from_proto(envelope.payload.trigger);
- Ok::<_, anyhow::Error>(this.format(buffers, false, trigger, cx))
- })??;
-
- let project_transaction = format.await?;
- let project_transaction = this.update(&mut cx, |this, cx| {
- this.serialize_project_transaction_for_peer(project_transaction, sender_id, cx)
- })?;
- Ok(proto::FormatBuffersResponse {
- transaction: Some(project_transaction),
- })
- }
-
async fn handle_task_context_for_location(
project: Model<Self>,
envelope: TypedEnvelope<proto::TaskContextForLocation>,
@@ -4,7 +4,7 @@ use futures::{future, StreamExt};
use gpui::{AppContext, SemanticVersion, UpdateGlobal};
use http_client::Url;
use language::{
- language_settings::{AllLanguageSettings, LanguageSettingsContent},
+ language_settings::{language_settings, AllLanguageSettings, LanguageSettingsContent},
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter,
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
};