Fix when agent-powered merge conflict button shows up (#54791)

This PR fixes the logic to dismiss the "resolve merge conflict with
agent" button. Previously, we were just observing
`merge_heads_by_conflicted_path`, which seems to be intentionally
sticky, preserving the conflicted paths until changes are either
committed or aborted. This would make the button to resolve conflicts
show up even _after_ the changes get resolved. Now, we're checking
whether paths are _currently_ conflicted (`is_conflicted()`), and if
they are not, we don't display the button, even though the resolution
might have not been committed or aborted yet.

As a bonus, in this PR, I'm also putting the resolve conflict button
_before_ the activity indicator, so as to avoid bounciness, and did a
quick polish of the activity indicator button itself, by using the
`Button` component.

Release Notes:

- Fixed a bug with the merge conflict "resolve with agent" button where
it would be displayed even though all conflicts have already been
resolved.
This commit is contained in:
Danilo Leal 2026-04-24 11:41:44 -03:00 committed by GitHub
parent f59c122bee
commit f2e9c5a025
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 40 deletions

View file

@ -3,8 +3,8 @@ use editor::Editor;
use extension_host::{ExtensionOperation, ExtensionStore};
use futures::StreamExt;
use gpui::{
App, Context, CursorStyle, Entity, EventEmitter, InteractiveElement as _, ParentElement as _,
Render, SharedString, StatefulInteractiveElement, Styled, Window, actions,
App, Context, Entity, EventEmitter, InteractiveElement as _, ParentElement as _, Render,
SharedString, Styled, Window, actions,
};
use language::{
BinaryStatus, LanguageRegistry, LanguageServerId, LanguageServerName,
@ -22,10 +22,7 @@ use std::{
sync::Arc,
time::{Duration, Instant},
};
use ui::{
ButtonLike, CommonAnimationExt, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip,
prelude::*,
};
use ui::{CommonAnimationExt, ContextMenu, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
use util::truncate_and_trailoff;
use workspace::{StatusItemView, Workspace, item::ItemHandle};
@ -720,43 +717,39 @@ impl Render for ActivityIndicator {
};
let activity_indicator = cx.entity().downgrade();
let truncate_content = content.message.len() > MAX_MESSAGE_LEN;
result.gap_2().child(
PopoverMenu::new("activity-indicator-popover")
.trigger(
ButtonLike::new("activity-indicator-trigger").child(
h_flex()
.id("activity-indicator-status")
.gap_2()
.children(content.icon)
.map(|button| {
if truncate_content {
button
.child(
Label::new(truncate_and_trailoff(
&content.message,
MAX_MESSAGE_LEN,
))
.size(LabelSize::Small),
)
.tooltip(Tooltip::text(content.message))
} else {
button
.child(Label::new(content.message).size(LabelSize::Small))
.when_some(
content.tooltip_message,
|this, tooltip_message| {
this.tooltip(Tooltip::text(tooltip_message))
},
)
}
Button::new("activity-indicator-trigger", {
if truncate_content {
truncate_and_trailoff(&content.message, MAX_MESSAGE_LEN)
} else {
content.message.clone()
}
})
.label_size(LabelSize::Small)
.when(content.icon.is_some(), |this| {
this.start_icon(
Icon::new(IconName::LoadCircle)
.color(Color::Muted)
.size(IconSize::Small),
)
})
.map(|button| {
if truncate_content {
button.tooltip(Tooltip::text(content.message))
} else {
button.when_some(content.tooltip_message, |this, tooltip_message| {
this.tooltip(Tooltip::text(tooltip_message))
})
.when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, window, cx| {
handler(this, window, cx);
}))
.cursor(CursorStyle::PointingHand)
}),
),
}
})
.when_some(content.on_click, |this, handler| {
this.on_click(cx.listener(move |this, _, window, cx| {
handler(this, window, cx);
}))
}),
)
.anchor(gpui::Anchor::BottomLeft)
.menu(move |window, cx| {

View file

@ -418,6 +418,12 @@ fn collect_conflicted_file_paths(project: &Project, cx: &App) -> Vec<String> {
for repo in git_store.repositories().values() {
let snapshot = repo.read(cx).snapshot();
for (repo_path, _) in snapshot.merge.merge_heads_by_conflicted_path.iter() {
let is_currently_conflicted = snapshot
.status_for_path(repo_path)
.is_some_and(|entry| entry.status.is_conflicted());
if !is_currently_conflicted {
continue;
}
if let Some(project_path) = repo.read(cx).repo_path_to_project_path(repo_path, cx) {
paths.push(
project_path

View file

@ -573,8 +573,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut App) {
status_bar.add_left_item(lsp_button, window, cx);
status_bar.add_left_item(diagnostic_summary, window, cx);
status_bar.add_left_item(active_file_name, window, cx);
status_bar.add_left_item(activity_indicator, window, cx);
status_bar.add_left_item(merge_conflict_indicator, window, cx);
status_bar.add_left_item(activity_indicator, window, cx);
status_bar.add_right_item(edit_prediction_ui, window, cx);
status_bar.add_right_item(active_buffer_encoding, window, cx);
status_bar.add_right_item(active_buffer_language, window, cx);