Show formatting failure (#9229)

This fixes #8072 and #9061 by surfacing formatting errors in the
activity indicator.

It shows a message in the activity indicator if the last attempt
to format a buffer failed.

It only keeps track of the last attempt, so any further formatting
that succeeds will reset or update the error message.

I chose to only keep track of that, because everything else (keeping
track of formatting state per buffer, per project, per worktree) seems
complicated with little benefit, since we'd have to keep track of that
state, update it, clean it, etc.

We can still do that should we decide that we need to keep track
of the state on a per-buffer basis, but I think for now this is a
good, simple solution.

This also changes the `OpenLog` action to scroll to the end of the
buffer
and to not mark the buffer as dirty.


Release Notes:

- Added message to activity indicator if last attempt to format a buffer
failed. Message will get reset when next formatting succeeds. Clicking
on message opens log with more information.
([#8072](https://github.com/zed-industries/zed/issues/8072) and
[#9061](https://github.com/zed-industries/zed/issues/9061)).
- Changed `zed: Open Log` action to not mark the opened log file as
dirty and to always scroll to the bottom of the log.


https://github.com/zed-industries/zed/assets/1185253/951fb9ac-8b8b-483a-a46d-712e52878a4d
This commit is contained in:
Thorsten Ball 2024-03-12 16:30:08 +01:00 committed by GitHub
parent 39a0841ea8
commit 8c87b349dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 334 additions and 290 deletions

View file

@ -241,6 +241,17 @@ impl ActivityIndicator {
};
}
// Show any formatting failure
if let Some(failure) = self.project.read(cx).last_formatting_failure() {
return Content {
icon: Some(WARNING_ICON),
message: format!("Formatting failed: {}. Click to see logs.", failure),
on_click: Some(Arc::new(|_, cx| {
cx.dispatch_action(Box::new(workspace::OpenLog));
})),
};
}
// Show any application auto-update info.
if let Some(updater) = &self.auto_updater {
return match &updater.read(cx).status() {

View file

@ -135,6 +135,7 @@ pub struct Project {
language_servers: HashMap<LanguageServerId, LanguageServerState>,
language_server_ids: HashMap<(WorktreeId, LanguageServerName), LanguageServerId>,
language_server_statuses: BTreeMap<LanguageServerId, LanguageServerStatus>,
last_formatting_failure: Option<String>,
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
language_server_watched_paths: HashMap<LanguageServerId, HashMap<WorktreeId, GlobSet>>,
client: Arc<client::Client>,
@ -606,6 +607,7 @@ impl Project {
language_servers: Default::default(),
language_server_ids: HashMap::default(),
language_server_statuses: Default::default(),
last_formatting_failure: None,
last_workspace_edits_by_language_server: Default::default(),
language_server_watched_paths: HashMap::default(),
buffers_being_formatted: Default::default(),
@ -738,6 +740,7 @@ impl Project {
)
})
.collect(),
last_formatting_failure: None,
last_workspace_edits_by_language_server: Default::default(),
language_server_watched_paths: HashMap::default(),
opened_buffers: Default::default(),
@ -3992,6 +3995,10 @@ impl Project {
self.language_server_statuses.values()
}
pub fn last_formatting_failure(&self) -> Option<&str> {
self.last_formatting_failure.as_deref()
}
pub fn update_diagnostics(
&mut self,
language_server_id: LanguageServerId,
@ -4297,7 +4304,7 @@ impl Project {
cx: &mut ModelContext<Project>,
) -> Task<anyhow::Result<ProjectTransaction>> {
if self.is_local() {
let mut buffers_with_paths = buffers
let buffers_with_paths = buffers
.into_iter()
.filter_map(|buffer_handle| {
let buffer = buffer_handle.read(cx);
@ -4308,6 +4315,62 @@ impl Project {
.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 {
let mut project_transaction = ProjectTransaction::default();
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"))?;
project_transaction = this
.update(&mut cx, |this, cx| {
this.deserialize_project_transaction(response, push_to_history, cx)
})?
.await?;
}
Ok(project_transaction)
})
}
}
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.
project.update(&mut cx, |this, cx| {
@ -4511,13 +4574,10 @@ impl Project {
}
(Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
if let Some(new_operation) =
prettier_support::format_with_prettier(&project, buffer, &mut cx)
.await
prettier_support::format_with_prettier(&project, buffer, &mut cx).await
{
format_operation = Some(new_operation);
} else if let Some((language_server, buffer_abs_path)) =
server_and_buffer
{
} else if let Some((language_server, buffer_abs_path)) = server_and_buffer {
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
&project,
@ -4534,8 +4594,7 @@ impl Project {
}
(Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => {
if let Some(new_operation) =
prettier_support::format_with_prettier(&project, buffer, &mut cx)
.await
prettier_support::format_with_prettier(&project, buffer, &mut cx).await
{
format_operation = Some(new_operation);
}
@ -4586,36 +4645,6 @@ impl Project {
}
Ok(project_transaction)
})
} else {
let remote_id = self.remote_id();
let client = self.client.clone();
cx.spawn(move |this, mut cx| async move {
let mut project_transaction = ProjectTransaction::default();
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"))?;
project_transaction = this
.update(&mut cx, |this, cx| {
this.deserialize_project_transaction(response, push_to_history, cx)
})?
.await?;
}
Ok(project_transaction)
})
}
}
async fn format_via_lsp(

View file

@ -4124,6 +4124,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
}
actions!(collab, [OpenChannelNotes]);
actions!(zed, [OpenLog]);
async fn join_channel_internal(
channel_id: ChannelId,

View file

@ -7,7 +7,7 @@ use assistant::AssistantPanel;
use breadcrumbs::Breadcrumbs;
use client::ZED_URL_SCHEME;
use collections::VecDeque;
use editor::{Editor, MultiBuffer};
use editor::{scroll::Autoscroll, Editor, MultiBuffer};
use gpui::{
actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, PromptLevel,
TitlebarOptions, View, ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
@ -41,7 +41,7 @@ use vim::VimModeSetting;
use welcome::BaseKeymap;
use workspace::{
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
open_new, AppState, NewFile, NewWindow, Toast, Workspace, WorkspaceSettings,
open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
};
use workspace::{notifications::DetachAndPromptErr, Pane};
use zed_actions::{OpenBrowser, OpenSettings, OpenZedUrl, Quit};
@ -62,7 +62,6 @@ actions!(
OpenLicenses,
OpenLocalSettings,
OpenLocalTasks,
OpenLog,
OpenTasks,
OpenTelemetryLog,
ResetBufferFontSize,
@ -561,21 +560,25 @@ fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
};
let project = workspace.project().clone();
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.update(cx, |project, cx| project.create_buffer(&log, None, cx))
.expect("creating buffers on a local workspace always succeeds");
buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
let buffer = cx.new_model(|cx| {
MultiBuffer::singleton(buffer, cx).with_title("Log".into())
});
workspace.add_item_to_active_pane(
Box::new(
cx.new_view(|cx| {
Editor::for_multibuffer(buffer, Some(project), cx)
}),
),
cx,
);
let editor =
cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx));
editor.update(cx, |editor, cx| {
let last_multi_buffer_offset = editor.buffer().read(cx).len(cx);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(Some(
last_multi_buffer_offset..last_multi_buffer_offset,
));
})
});
workspace.add_item_to_active_pane(Box::new(editor), cx);
})
.log_err();
})