Improve the generate commit message design (#26233)
[WIP] Release Notes: - N/A --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
parent
3345666557
commit
8f4b7aa5db
6 changed files with 181 additions and 114 deletions
10
assets/icons/ai_edit.svg
Normal file
10
assets/icons/ai_edit.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5871 5.40624C12.8514 5.14195 13 4.78346 13 4.40965C13 4.03583 12.8516 3.67731 12.5873 3.41295C12.323 3.14859 11.9645 3.00005 11.5907 3C11.2169 2.99995 10.8584 3.14841 10.594 3.4127L3.92098 10.0874C3.80488 10.2031 3.71903 10.3456 3.67097 10.5024L3.01047 12.6784C2.99754 12.7217 2.99657 12.7676 3.00764 12.8113C3.01872 12.8551 3.04143 12.895 3.07337 12.9269C3.1053 12.9588 3.14528 12.9815 3.18905 12.9925C3.23282 13.0035 3.27875 13.0024 3.32197 12.9894L5.49849 12.3294C5.65508 12.2818 5.79758 12.1964 5.91349 12.0809L12.5871 5.40624Z" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 4L12 6" stroke="black" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.38818 3.53598V2.53598" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.56982 12.6995L9.56982 13.6995" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.38818 6.53598H3.38818" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5698 9.69949L12.5698 9.69949" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.38818 4.53598L3.38818 3.53598" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5698 11.6995L12.5698 12.6995" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -15,5 +15,3 @@ Follow good Git style:
|
|||
- Use the imperative mood in the subject line
|
||||
- Wrap the body at 72 characters
|
||||
- Keep the body short and concise (omit it entirely if not useful)
|
||||
|
||||
Here are the changes in this commit:
|
||||
|
|
|
@ -304,8 +304,11 @@ impl CommitModal {
|
|||
git_panel.update(cx, |git_panel, cx| git_panel.editor_focus_handle(cx));
|
||||
|
||||
let commit_button = panel_filled_button(commit_label)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(tooltip, &Commit, &panel_editor_focus_handle, window, cx)
|
||||
.tooltip({
|
||||
let panel_editor_focus_handle = panel_editor_focus_handle.clone();
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(tooltip, &Commit, &panel_editor_focus_handle, window, cx)
|
||||
}
|
||||
})
|
||||
.disabled(!can_commit)
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
|
@ -328,8 +331,8 @@ impl CommitModal {
|
|||
h_flex()
|
||||
.gap_1()
|
||||
.child(branch_picker)
|
||||
.children(co_authors)
|
||||
.child(generate_commit_message),
|
||||
.children(generate_commit_message)
|
||||
.children(co_authors),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
|
|
|
@ -23,7 +23,7 @@ use git::repository::{
|
|||
ResetMode, Upstream, UpstreamTracking, UpstreamTrackingStatus,
|
||||
};
|
||||
use git::{repository::RepoPath, status::FileStatus, Commit, ToggleStaged};
|
||||
use git::{RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||
use git::{ExpandCommitEditor, RestoreTrackedFiles, StageAll, TrashUntrackedFiles, UnstageAll};
|
||||
use gpui::{
|
||||
actions, anchored, deferred, hsla, percentage, point, uniform_list, Action, Animation,
|
||||
AnimationExt as _, AnyView, BoxShadow, ClickEvent, Corner, DismissEvent, Entity, EventEmitter,
|
||||
|
@ -35,7 +35,7 @@ use gpui::{
|
|||
use itertools::Itertools;
|
||||
use language::{Buffer, File};
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use menu::{Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrevious};
|
||||
use multi_buffer::ExcerptInfo;
|
||||
|
@ -78,6 +78,7 @@ actions!(
|
|||
FocusEditor,
|
||||
FocusChanges,
|
||||
ToggleFillCoAuthors,
|
||||
GenerateCommitMessage
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -124,6 +125,9 @@ pub fn init(cx: &mut App) {
|
|||
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
|
||||
workspace.toggle_panel_focus::<GitPanel>(window, cx);
|
||||
});
|
||||
workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| {
|
||||
CommitModal::toggle(workspace, window, cx)
|
||||
});
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
|
@ -1438,17 +1442,11 @@ impl GitPanel {
|
|||
}
|
||||
|
||||
/// Generates a commit message using an LLM.
|
||||
fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
|
||||
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
|
||||
return;
|
||||
pub fn generate_commit_message(&mut self, cx: &mut Context<Self>) {
|
||||
let model = match current_language_model(cx) {
|
||||
Some(value) => value,
|
||||
None => return,
|
||||
};
|
||||
let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !provider.is_authenticated(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(repo) = self.active_repository.as_ref() else {
|
||||
return;
|
||||
|
@ -1478,17 +1476,30 @@ impl GitPanel {
|
|||
});
|
||||
|
||||
let mut diff_text = diff.await??;
|
||||
|
||||
const ONE_MB: usize = 1_000_000;
|
||||
if diff_text.len() > ONE_MB {
|
||||
diff_text = diff_text.chars().take(ONE_MB).collect()
|
||||
}
|
||||
|
||||
let subject = this.update(&mut cx, |this, cx| {
|
||||
this.commit_editor.read(cx).text(cx).lines().next().map(ToOwned::to_owned).unwrap_or_default()
|
||||
})?;
|
||||
|
||||
let text_empty = subject.trim().is_empty();
|
||||
|
||||
let content = if text_empty {
|
||||
format!("{PROMPT}\nHere are the changes in this commit:\n{diff_text}")
|
||||
} else {
|
||||
format!("{PROMPT}\nHere is the user's subject line:\n{subject}\nHere are the changes in this commit:\n{diff_text}\n")
|
||||
};
|
||||
|
||||
const PROMPT: &str = include_str!("commit_message_prompt.txt");
|
||||
|
||||
let request = LanguageModelRequest {
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![format!("{PROMPT}\n{diff_text}").into()],
|
||||
content: vec![content.into()],
|
||||
cache: false,
|
||||
}],
|
||||
tools: Vec::new(),
|
||||
|
@ -1499,6 +1510,15 @@ impl GitPanel {
|
|||
let stream = model.stream_completion_text(request, &cx);
|
||||
let mut messages = stream.await?;
|
||||
|
||||
if !text_empty {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.commit_message_buffer(cx).update(cx, |buffer, cx| {
|
||||
let insert_position = buffer.anchor_before(buffer.len());
|
||||
buffer.edit([(insert_position..insert_position, "\n")], None, cx)
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
while let Some(message) = messages.stream.next().await {
|
||||
let text = message?;
|
||||
|
||||
|
@ -2178,64 +2198,82 @@ impl GitPanel {
|
|||
self.has_staged_changes()
|
||||
}
|
||||
|
||||
pub(crate) fn render_generate_commit_message_button(&self, cx: &Context<Self>) -> AnyElement {
|
||||
if self.generate_commit_message_task.is_some() {
|
||||
return Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
)
|
||||
.into_any_element();
|
||||
}
|
||||
pub(crate) fn render_generate_commit_message_button(
|
||||
&self,
|
||||
cx: &Context<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
current_language_model(cx).is_some().then(|| {
|
||||
if self.generate_commit_message_task.is_some() {
|
||||
return h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
Label::new("Generating Commit...")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
IconButton::new("generate-commit-message", IconName::ZedAssistant)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Generate commit message",
|
||||
&git::GenerateCommitMessage,
|
||||
&self.commit_editor.focus_handle(cx),
|
||||
))
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.generate_commit_message(cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
IconButton::new("generate-commit-message", IconName::AiEdit)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::for_action_title_in(
|
||||
"Generate Commit Message",
|
||||
&git::GenerateCommitMessage,
|
||||
&self.commit_editor.focus_handle(cx),
|
||||
))
|
||||
.on_click(cx.listener(move |this, _event, _window, cx| {
|
||||
this.generate_commit_message(cx);
|
||||
}))
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn render_co_authors(&self, cx: &Context<Self>) -> Option<AnyElement> {
|
||||
let potential_co_authors = self.potential_co_authors(cx);
|
||||
if potential_co_authors.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
IconButton::new("co-authors", IconName::Person)
|
||||
.icon_color(Color::Disabled)
|
||||
.selected_icon_color(Color::Selected)
|
||||
.toggle_state(self.add_coauthors)
|
||||
.tooltip(move |_, cx| {
|
||||
let title = format!(
|
||||
"Add co-authored-by:{}{}",
|
||||
if potential_co_authors.len() == 1 {
|
||||
""
|
||||
} else {
|
||||
"\n"
|
||||
},
|
||||
potential_co_authors
|
||||
.iter()
|
||||
.map(|(name, email)| format!(" {} <{}>", name, email))
|
||||
.join("\n")
|
||||
);
|
||||
Tooltip::simple(title, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.add_coauthors = !this.add_coauthors;
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
// if potential_co_authors.is_empty() {
|
||||
// None
|
||||
// } else {
|
||||
Some(
|
||||
IconButton::new("co-authors", IconName::Person)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_color(Color::Disabled)
|
||||
.selected_icon_color(Color::Selected)
|
||||
.toggle_state(self.add_coauthors)
|
||||
.tooltip(move |_, cx| {
|
||||
let title = format!(
|
||||
"Add co-authored-by:{}{}",
|
||||
if potential_co_authors.len() == 1 {
|
||||
""
|
||||
} else {
|
||||
"\n"
|
||||
},
|
||||
potential_co_authors
|
||||
.iter()
|
||||
.map(|(name, email)| format!(" {} <{}>", name, email))
|
||||
.join("\n")
|
||||
);
|
||||
Tooltip::simple(title, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.add_coauthors = !this.add_coauthors;
|
||||
cx.notify();
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn configure_commit_button(&self, cx: &mut Context<Self>) -> (bool, &'static str) {
|
||||
|
@ -2292,19 +2330,18 @@ impl GitPanel {
|
|||
let panel_editor_style = panel_editor_style(true, window, cx);
|
||||
|
||||
let enable_coauthors = self.render_co_authors(cx);
|
||||
|
||||
let title = self.commit_button_title();
|
||||
|
||||
let editor_focus_handle = self.commit_editor.focus_handle(cx);
|
||||
let commit_tooltip_focus_handle = editor_focus_handle.clone();
|
||||
let expand_tooltip_focus_handle = editor_focus_handle.clone();
|
||||
|
||||
let branch = active_repository.read(cx).current_branch().cloned();
|
||||
|
||||
let footer_size = px(32.);
|
||||
let gap = px(8.0);
|
||||
|
||||
let max_height = window.line_height() * 5. + gap + footer_size;
|
||||
|
||||
let expand_button_size = px(16.);
|
||||
|
||||
let git_panel = cx.entity().clone();
|
||||
let display_name = SharedString::from(Arc::from(
|
||||
active_repository
|
||||
|
@ -2325,9 +2362,9 @@ impl GitPanel {
|
|||
.id("commit-editor-container")
|
||||
.relative()
|
||||
.h(max_height)
|
||||
// .w_full()
|
||||
// .border_t_1()
|
||||
// .border_color(cx.theme().colors().border)
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.cursor_text()
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
|
@ -2338,54 +2375,66 @@ impl GitPanel {
|
|||
.id("commit-footer")
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.right_2()
|
||||
.gap_0p5()
|
||||
.left_0()
|
||||
.w_full()
|
||||
.px_2()
|
||||
.h(footer_size)
|
||||
.flex_none()
|
||||
.children(enable_coauthors)
|
||||
.child(self.render_generate_commit_message_button(cx))
|
||||
.justify_between()
|
||||
.child(
|
||||
panel_filled_button(title)
|
||||
.tooltip(move |window, cx| {
|
||||
if can_commit {
|
||||
Tooltip::for_action_in(
|
||||
tooltip,
|
||||
&Commit,
|
||||
&editor_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::simple(tooltip, cx)
|
||||
}
|
||||
})
|
||||
.disabled(!can_commit || self.modal_open)
|
||||
.on_click({
|
||||
cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
telemetry::event!(
|
||||
"Git Committed",
|
||||
source = "Git Panel"
|
||||
);
|
||||
this.commit_changes(window, cx)
|
||||
self.render_generate_commit_message_button(cx)
|
||||
.unwrap_or_else(|| div().into_any_element()),
|
||||
)
|
||||
.child(
|
||||
h_flex().gap_0p5().children(enable_coauthors).child(
|
||||
panel_filled_button(title)
|
||||
.tooltip(move |window, cx| {
|
||||
if can_commit {
|
||||
Tooltip::for_action_in(
|
||||
tooltip,
|
||||
&Commit,
|
||||
&commit_tooltip_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::simple(tooltip, cx)
|
||||
}
|
||||
})
|
||||
}),
|
||||
.disabled(!can_commit || self.modal_open)
|
||||
.on_click({
|
||||
cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
this.commit_changes(window, cx)
|
||||
})
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
// .when(!self.modal_open, |el| {
|
||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
|
||||
.child(
|
||||
div()
|
||||
.pr_2p5()
|
||||
.child(EditorElement::new(&self.commit_editor, panel_editor_style)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.top_1()
|
||||
.top_2()
|
||||
.right_2()
|
||||
.opacity(0.5)
|
||||
.hover(|this| this.opacity(1.0))
|
||||
.w(expand_button_size)
|
||||
.child(
|
||||
panel_icon_button("expand-commit-editor", IconName::Maximize)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.width(expand_button_size.into())
|
||||
.size(ui::ButtonSize::Default)
|
||||
.tooltip(move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Open Commit Modal",
|
||||
&git::ExpandCommitEditor,
|
||||
&expand_tooltip_focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
move |_, _, window, cx| {
|
||||
window.dispatch_action(
|
||||
|
@ -2963,6 +3012,12 @@ impl GitPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn current_language_model(cx: &Context<'_, GitPanel>) -> Option<Arc<dyn LanguageModel>> {
|
||||
let provider = LanguageModelRegistry::read_global(cx).active_provider()?;
|
||||
let model = LanguageModelRegistry::read_global(cx).active_model()?;
|
||||
provider.is_authenticated(cx).then(|| model)
|
||||
}
|
||||
|
||||
impl Render for GitPanel {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let project = self.project.read(cx);
|
||||
|
|
|
@ -128,6 +128,7 @@ pub enum IconName {
|
|||
AiBedrock,
|
||||
AiAnthropicHosted,
|
||||
AiDeepSeek,
|
||||
AiEdit,
|
||||
AiGoogle,
|
||||
AiLmStudio,
|
||||
AiMistral,
|
||||
|
|
|
@ -35,7 +35,7 @@ use futures::{
|
|||
Future, FutureExt, StreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size,
|
||||
action_as, actions, canvas, deferred, impl_action_as, impl_actions, point, relative, size,
|
||||
transparent_black, Action, AnyView, AnyWeakView, App, AsyncApp, AsyncWindowContext, Bounds,
|
||||
Context, CursorStyle, Decorations, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle,
|
||||
Focusable, Global, Hsla, KeyContext, Keystroke, ManagedView, MouseButton, PathPromptOptions,
|
||||
|
@ -5546,7 +5546,7 @@ impl Render for Workspace {
|
|||
.children(self.render_notifications(window, cx)),
|
||||
)
|
||||
.child(self.status_bar.clone())
|
||||
.child(self.modal_layer.clone())
|
||||
.child(deferred(self.modal_layer.clone()))
|
||||
.child(self.toast_layer.clone()),
|
||||
),
|
||||
window,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue