Skip to content

Commit 193b911

Browse files
committed
feat(commit): improve inputs logic
Fix required and optional inputs skipping behaveioiur: For optional fields (scope, body, footer): - Press Enter on empty line skips the field - Press Enter after typing text adds new line for multiline input - Alt+Enter finishes multiline input For required fields (subject): - Press Enter on empty line shows red error message " This field is required. Please enter some content." and prevents newline - Press Enter after typing text adds new line for multiline input - Alt+Enter finishes input
1 parent 879612d commit 193b911

File tree

3 files changed

+148
-12
lines changed

3 files changed

+148
-12
lines changed

commitizen/commands/commit.py

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
from typing import TypedDict
1010

1111
import questionary
12+
import questionary.prompts.text
13+
from prompt_toolkit.key_binding import KeyBindings
14+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
15+
from prompt_toolkit.keys import Keys
1216

1317
from commitizen import factory, git, out
1418
from commitizen.config import BaseConfig
@@ -83,21 +87,114 @@ def _prompt_commit_questions(self) -> str:
8387
raise CustomError(root_err.__str__())
8488
raise err
8589
elif question["type"] == "input" and question.get("multiline", False):
86-
print(
87-
"\033[90m💡 Multiline input:\n Press Enter for new lines and Esc+Enter to finish\033[0m \n \033[90mor (Finish with 'Alt+Enter' or 'Esc then Enter')\033[0m"
90+
is_optional = (
91+
question.get("default") == ""
92+
or "skip" in question.get("message", "").lower()
8893
)
94+
95+
if is_optional:
96+
print(
97+
"\033[90m💡 Multiline input:\n Press Enter on empty line to skip, Enter after text for new lines, Alt+Enter to finish\033[0m"
98+
)
99+
else:
100+
print(
101+
"\033[90m💡 Multiline input:\n Press Enter for new lines and Alt+Enter to finish\033[0m"
102+
)
103+
104+
# Create custom multiline input with Enter-on-empty behavior for optional fields
105+
89106
multiline_question = question.copy()
90107
multiline_question["multiline"] = True
91-
try:
92-
answer = questionary.prompt([multiline_question], style=cz.style)
93-
if not answer:
94-
raise NoAnswersError()
95-
answers.update(answer)
96-
except ValueError as err:
97-
root_err = err.__context__
98-
if isinstance(root_err, CzException):
99-
raise CustomError(root_err.__str__())
100-
raise err
108+
109+
if is_optional:
110+
# Create custom key bindings for optional fields
111+
bindings = KeyBindings()
112+
113+
@bindings.add(Keys.Enter)
114+
def _(event: KeyPressEvent) -> None:
115+
buffer = event.current_buffer
116+
# If buffer is completely empty, submit
117+
if not buffer.text.strip():
118+
event.app.exit(result=buffer.text)
119+
else:
120+
# If there's text, add new line
121+
buffer.newline()
122+
123+
# Use the text prompt directly with custom bindings
124+
try:
125+
result = questionary.prompts.text.text(
126+
message=question["message"],
127+
multiline=True,
128+
style=cz.style,
129+
key_bindings=bindings,
130+
).ask()
131+
132+
field_name = question["name"]
133+
if result is None:
134+
result = question.get("default", "")
135+
136+
# Apply filter if present
137+
if "filter" in question:
138+
result = question["filter"](result)
139+
140+
answer = {field_name: result}
141+
answers.update(answer)
142+
143+
except Exception:
144+
# Fallback to standard behavior if custom approach fails
145+
answer = questionary.prompt(
146+
[multiline_question], style=cz.style
147+
)
148+
if not answer:
149+
raise NoAnswersError()
150+
answers.update(answer)
151+
else:
152+
# Required fields - don't allow newline on empty first line and show error
153+
bindings = KeyBindings()
154+
155+
@bindings.add(Keys.Enter)
156+
def _(event: KeyPressEvent) -> None:
157+
buffer = event.current_buffer
158+
# If buffer is completely empty (no content at all), show error and don't allow newline
159+
if not buffer.text.strip():
160+
# Show error message with prompt
161+
print(
162+
"\n\033[91m⚠ This field is required. Please enter some content or press Ctrl+C to abort.\033[0m"
163+
)
164+
print("> ", end="", flush=True)
165+
# Don't do anything - require content first
166+
pass
167+
else:
168+
# If there's text, add new line
169+
buffer.newline()
170+
171+
try:
172+
result = questionary.prompts.text.text(
173+
message=question["message"],
174+
multiline=True,
175+
style=cz.style,
176+
key_bindings=bindings,
177+
).ask()
178+
179+
field_name = question["name"]
180+
if result is None:
181+
result = ""
182+
183+
# Apply filter if present
184+
if "filter" in question:
185+
result = question["filter"](result)
186+
187+
answer = {field_name: result}
188+
answers.update(answer)
189+
190+
except Exception:
191+
# Fallback to standard behavior if custom approach fails
192+
answer = questionary.prompt(
193+
[multiline_question], style=cz.style
194+
)
195+
if not answer:
196+
raise NoAnswersError()
197+
answers.update(answer)
101198
else:
102199
try:
103200
answer = questionary.prompt([question], style=cz.style)

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ This standardization makes your commit history more readable and meaningful, whi
4242
### Features
4343

4444
- Interactive CLI for standardized commits with default [Conventional Commits][conventional_commits] support
45+
- **Enhanced multiline input** with smart behavior for required and optional fields
4546
- Intelligent [version bumping](https://commitizen-tools.github.io/commitizen/commands/bump/) using [Semantic Versioning][semver]
4647
- Automatic [keep a changelog][keepchangelog] generation
4748
- Built-in commit validation with pre-commit hooks

docs/commands/commit.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,44 @@ cz commit --write-message-to-file COMMIT_MSG_FILE
2828

2929
This can be combined with `--dry-run` to only write the message without creating a commit. This is particularly useful for [automatically preparing commit messages](../tutorials/auto_prepare_commit_message.md).
3030

31+
## Multiline Input Support
32+
33+
Commitizen now supports enhanced multiline input for commit messages, making it easier to create detailed, well-structured commits.
34+
35+
### How It Works
36+
37+
When prompted for input during `cz commit`, you can now use multiline input with smart behavior:
38+
39+
#### For Optional Fields (scope, body, footer)
40+
- **Enter on empty line** → Skips the field
41+
- **Enter after typing content** → Adds a new line for multiline input
42+
- **Alt+Enter** → Finishes and submits the input
43+
44+
#### For Required Fields (subject)
45+
- **Enter on empty line** → Shows error message with options:
46+
```
47+
⚠ This field is required. Please enter some content or press Ctrl+C to abort.
48+
>
49+
```
50+
- **Enter after typing content** → Adds a new line for multiline input
51+
- **Alt+Enter** → Finishes and submits the input
52+
- **Ctrl+C** → Aborts the commit session
53+
54+
### Example Usage
55+
56+
```sh
57+
cz commit
58+
```
59+
60+
During the interactive process:
61+
62+
1. **Commit Type**: Select from the list (e.g., `feat`, `fix`, `docs`)
63+
2. **Scope** (optional): Press Enter to skip, or type scope and use Enter for multiline
64+
3. **Subject** (required): Must enter content, can use Enter for multiline, Alt+Enter to finish
65+
4. **Body** (optional): Press Enter to skip, or add detailed description with multiline support
66+
5. **Breaking Change**: Yes/No confirmation
67+
6. **Footer** (optional): Press Enter to skip, or add references/notes with multiline support
68+
3169
## Advanced Features
3270

3371
### Git Command Options

0 commit comments

Comments
 (0)