Skip to content

Commit 108860f

Browse files
committed
feat(commit-input): support multiline input using backslash
1 parent 6a2c806 commit 108860f

File tree

4 files changed

+74
-27
lines changed

4 files changed

+74
-27
lines changed

commitizen/commands/commit.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from commitizen import factory, git, out
1414
from commitizen.config import BaseConfig
1515
from commitizen.cz.exceptions import CzException
16-
from commitizen.cz.utils import get_backup_file_path
16+
from commitizen.cz.utils import get_backup_file_path, get_input_with_continuation
1717
from commitizen.exceptions import (
1818
CommitError,
1919
CommitMessageLengthExceededError,
@@ -66,18 +66,41 @@ def _prompt_commit_questions(self) -> str:
6666
# Prompt user for the commit message
6767
cz = self.cz
6868
questions = cz.questions()
69-
for question in (q for q in questions if q["type"] == "list"):
70-
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
71-
try:
72-
answers = questionary.prompt(questions, style=cz.style)
73-
except ValueError as err:
74-
root_err = err.__context__
75-
if isinstance(root_err, CzException):
76-
raise CustomError(root_err.__str__())
77-
raise err
78-
79-
if not answers:
80-
raise NoAnswersError()
69+
answers = {}
70+
71+
# Handle questions one by one to support custom continuation
72+
for question in questions:
73+
if question["type"] == "list":
74+
question["use_shortcuts"] = self.config.settings["use_shortcuts"]
75+
try:
76+
answer = questionary.prompt([question], style=cz.style)
77+
if not answer:
78+
raise NoAnswersError()
79+
answers.update(answer)
80+
except ValueError as err:
81+
root_err = err.__context__
82+
if isinstance(root_err, CzException):
83+
raise CustomError(root_err.__str__())
84+
raise err
85+
elif question["type"] == "input" and question.get("continuation", False):
86+
print(f"\033[90m💡 Type backslash and press Enter for line continuation\033[0m")
87+
raw_answer = get_input_with_continuation(question["message"])
88+
if "filter" in question:
89+
processed_answer = question["filter"](raw_answer)
90+
else:
91+
processed_answer = raw_answer
92+
answers[question["name"]] = processed_answer
93+
else:
94+
try:
95+
answer = questionary.prompt([question], style=cz.style)
96+
if not answer:
97+
raise NoAnswersError()
98+
answers.update(answer)
99+
except ValueError as err:
100+
root_err = err.__context__
101+
if isinstance(root_err, CzException):
102+
raise CustomError(root_err.__str__())
103+
raise err
81104

82105
message = cz.message(answers)
83106
message_len = len(message.partition("\n")[0].strip())

commitizen/cz/conventional_commits/conventional_commits.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,26 +109,23 @@ def questions(self) -> list[CzQuestion]:
109109
{
110110
"type": "input",
111111
"name": "scope",
112-
"message": (
113-
"What is the scope of this change? (class or file name): (press [enter] to skip)\n"
114-
),
112+
"message": "What is the scope of this change? (class or file name): (press [enter] to skip)",
115113
"filter": _parse_scope,
114+
"multiline": True,
116115
},
117116
{
118117
"type": "input",
119118
"name": "subject",
120119
"filter": _parse_subject,
121-
"message": (
122-
"Write a short and imperative summary of the code changes: (lower case and no period)\n"
123-
),
120+
"message": "Write a short and imperative summary of the code changes: (lower case and no period)",
121+
"multiline": True,
124122
},
125123
{
126124
"type": "input",
127125
"name": "body",
128-
"message": (
129-
"Provide additional contextual information about the code changes: (press [enter] to skip)\n"
130-
),
131-
"filter": multiple_line_breaker,
126+
"message": "Provide additional contextual information about the code changes:\n(Type backslash and press Enter for line continuation, or use multiline input, or [Enter] to skip)",
127+
"multiline": True,
128+
"default": "",
132129
},
133130
{
134131
"type": "confirm",
@@ -139,10 +136,8 @@ def questions(self) -> list[CzQuestion]:
139136
{
140137
"type": "input",
141138
"name": "footer",
142-
"message": (
143-
"Footer. Information about Breaking Changes and "
144-
"reference issues that this commit closes: (press [enter] to skip)\n"
145-
),
139+
"message": "Footer. Information about Breaking Changes and reference issues that this commit closes: (press [enter] to skip)",
140+
"multiline": True,
146141
},
147142
]
148143

commitizen/cz/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import re
33
import tempfile
44

5+
import questionary
6+
57
from commitizen import git
68
from commitizen.cz import exceptions
79

@@ -18,6 +20,30 @@ def multiple_line_breaker(answer: str, sep: str = "|") -> str:
1820
return "\n".join(line.strip() for line in answer.split(sep) if line)
1921

2022

23+
def get_input_with_continuation(message: str) -> str:
24+
"""Get input with shell-like backslash line continuation."""
25+
lines = []
26+
prompt_msg = message + "\n"
27+
28+
while True:
29+
try:
30+
line = questionary.text(prompt_msg).ask()
31+
if line is None:
32+
return ""
33+
34+
if line.rstrip().endswith("\\"):
35+
lines.append(line.rstrip()[:-1].rstrip())
36+
prompt_msg = ">"
37+
else:
38+
lines.append(line)
39+
break
40+
41+
except KeyboardInterrupt:
42+
return ""
43+
44+
return "\n".join(lines)
45+
46+
2147
def strip_local_version(version: str) -> str:
2248
return _RE_LOCAL_VERSION.sub("", version)
2349

commitizen/question.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ class InputQuestion(TypedDict, total=False):
2020
name: str
2121
message: str
2222
filter: Callable[[str], str]
23+
multiline: bool
24+
default: str
25+
continuation: bool
2326

2427

2528
class ConfirmQuestion(TypedDict):

0 commit comments

Comments
 (0)