git: Rewrap commit messages just before committing instead of interactively (#32114)

Cole Miller created

Closes #27508 

Release Notes:

- Fixed unintuitive wrapping behavior when editing Git commit messages.

Change summary

assets/settings/default.json   |  4 ++
crates/git_ui/src/git_panel.rs | 50 +++++++++++++++++++++++++++++------
2 files changed, 44 insertions(+), 10 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -1457,7 +1457,9 @@
       "language_servers": ["erlang-ls", "!elp", "..."]
     },
     "Git Commit": {
-      "allow_rewrap": "anywhere"
+      "allow_rewrap": "anywhere",
+      "soft_wrap": "editor_width",
+      "preferred_line_length": 72
     },
     "Go": {
       "code_actions_on_format": {

crates/git_ui/src/git_panel.rs 🔗

@@ -383,7 +383,6 @@ pub(crate) fn commit_message_editor(
     commit_editor.set_show_gutter(false, cx);
     commit_editor.set_show_wrap_guides(false, cx);
     commit_editor.set_show_indent_guides(false, cx);
-    commit_editor.set_hard_wrap(Some(72), cx);
     let placeholder = placeholder.unwrap_or("Enter commit message".into());
     commit_editor.set_placeholder_text(placeholder, cx);
     commit_editor
@@ -1483,15 +1482,48 @@ impl GitPanel {
         }
     }
 
-    fn custom_or_suggested_commit_message(&self, cx: &mut Context<Self>) -> Option<String> {
+    fn custom_or_suggested_commit_message(
+        &self,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) -> Option<String> {
+        let git_commit_language = self.commit_editor.read(cx).language_at(0, cx);
         let message = self.commit_editor.read(cx).text(cx);
-
-        if !message.trim().is_empty() {
-            return Some(message);
+        if message.is_empty() {
+            return self
+                .suggest_commit_message(cx)
+                .filter(|message| !message.trim().is_empty());
+        } else if message.trim().is_empty() {
+            return None;
+        }
+        let buffer = cx.new(|cx| {
+            let mut buffer = Buffer::local(message, cx);
+            buffer.set_language(git_commit_language, cx);
+            buffer
+        });
+        let editor = cx.new(|cx| Editor::for_buffer(buffer, None, window, cx));
+        let wrapped_message = editor.update(cx, |editor, cx| {
+            editor.select_all(&Default::default(), window, cx);
+            editor.rewrap(&Default::default(), window, cx);
+            editor.text(cx)
+        });
+        if wrapped_message.trim().is_empty() {
+            return None;
         }
+        Some(wrapped_message)
+    }
 
-        self.suggest_commit_message(cx)
-            .filter(|message| !message.trim().is_empty())
+    fn has_commit_message(&self, cx: &mut Context<Self>) -> bool {
+        let text = self.commit_editor.read(cx).text(cx);
+        if !text.trim().is_empty() {
+            return true;
+        } else if text.is_empty() {
+            return self
+                .suggest_commit_message(cx)
+                .is_some_and(|text| !text.trim().is_empty());
+        } else {
+            return false;
+        }
     }
 
     pub(crate) fn commit_changes(
@@ -1520,7 +1552,7 @@ impl GitPanel {
             return;
         }
 
-        let commit_message = self.custom_or_suggested_commit_message(cx);
+        let commit_message = self.custom_or_suggested_commit_message(window, cx);
 
         let Some(mut message) = commit_message else {
             self.commit_editor.read(cx).focus_handle(cx).focus(window);
@@ -2832,7 +2864,7 @@ impl GitPanel {
             (false, "No changes to commit")
         } else if self.pending_commit.is_some() {
             (false, "Commit in progress")
-        } else if self.custom_or_suggested_commit_message(cx).is_none() {
+        } else if !self.has_commit_message(cx) {
             (false, "No commit message")
         } else if !self.has_write_access(cx) {
             (false, "You do not have write access to this project")