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
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue