Skip to content

Commit c2f4689

Browse files
committed
Fixed bug where async_alert() was overwriting readline incremental and non-incremental search prompts.
1 parent 16c6d30 commit c2f4689

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

cmd2/ansi.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,9 +1071,9 @@ def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_off
10711071
# Calculate how many terminal lines are taken up by all prompt lines except for the last one.
10721072
# That will be included in the input lines calculations since that is where the cursor is.
10731073
num_prompt_terminal_lines = 0
1074-
for line in prompt_lines[:-1]:
1075-
line_width = style_aware_wcswidth(line)
1076-
num_prompt_terminal_lines += int(line_width / terminal_columns) + 1
1074+
for prompt_line in prompt_lines[:-1]:
1075+
prompt_line_width = style_aware_wcswidth(prompt_line)
1076+
num_prompt_terminal_lines += int(prompt_line_width / terminal_columns) + 1
10771077

10781078
# Now calculate how many terminal lines are take up by the input
10791079
last_prompt_line = prompt_lines[-1]

cmd2/cmd2.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
rl_escape_prompt,
133133
rl_get_point,
134134
rl_get_prompt,
135+
rl_in_search_mode,
135136
rl_set_prompt,
136137
rl_type,
137138
rl_warning,
@@ -3295,6 +3296,12 @@ def _set_up_cmd2_readline(self) -> _SavedReadlineSettings:
32953296
"""
32963297
readline_settings = _SavedReadlineSettings()
32973298

3299+
if rl_type == RlType.GNU:
3300+
# To calculate line count when printing async_alerts, we rely on commands wider than
3301+
# the terminal to wrap across multiple lines. The default for horizontal-scroll-mode
3302+
# is "off" but a user may have overridden it in their readline initialization file.
3303+
readline.parse_and_bind("set horizontal-scroll-mode off")
3304+
32983305
if self._completion_supported():
32993306
# Set up readline for our tab completion needs
33003307
if rl_type == RlType.GNU:
@@ -5309,17 +5316,20 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
53095316
if new_prompt is not None:
53105317
self.prompt = new_prompt
53115318

5312-
# Check if the prompt to display has changed from what's currently displayed
53135319
cur_onscreen_prompt = rl_get_prompt()
5314-
new_onscreen_prompt = self.continuation_prompt if self._at_continuation_prompt else self.prompt
53155320

5316-
if new_onscreen_prompt != cur_onscreen_prompt:
5317-
update_terminal = True
5321+
# We won't change the onscreen prompt while readline is in search mode (e.g. Ctrl-r)
5322+
if not rl_in_search_mode():
5323+
# Check if the prompt to display has changed from what's currently displayed
5324+
new_onscreen_prompt = self.continuation_prompt if self._at_continuation_prompt else self.prompt
5325+
if new_onscreen_prompt != cur_onscreen_prompt:
5326+
update_terminal = True
5327+
rl_set_prompt(new_onscreen_prompt)
53185328

53195329
if update_terminal:
53205330
import shutil
53215331

5322-
# Generate the string which will replace the current prompt and input lines with the alert
5332+
# Print a string which replaces the current prompt and input lines with the alert
53235333
terminal_str = ansi.async_alert_str(
53245334
terminal_columns=shutil.get_terminal_size().columns,
53255335
prompt=cur_onscreen_prompt,
@@ -5333,9 +5343,6 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
53335343
elif rl_type == RlType.PYREADLINE:
53345344
readline.rl.mode.console.write(terminal_str)
53355345

5336-
# Update Readline's prompt before we redraw it
5337-
rl_set_prompt(new_onscreen_prompt)
5338-
53395346
# Redraw the prompt and input lines below the alert
53405347
rl_force_redisplay()
53415348

cmd2/rl_utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,32 @@ def rl_unescape_prompt(prompt: str) -> str:
276276
prompt = prompt.replace(escape_start, "").replace(escape_end, "")
277277

278278
return prompt
279+
280+
281+
def rl_in_search_mode() -> bool:
282+
"""Check if readline is doing either an incremental (e.g. Ctrl-r) or non-incremental (e.g. Esc-p) search"""
283+
if rl_type == RlType.GNU:
284+
# GNU Readline defines constants that we can use to determine if in search mode.
285+
# RL_STATE_ISEARCH 0x0000080
286+
# RL_STATE_NSEARCH 0x0000100
287+
IN_SEARCH_MODE = 0x0000180
288+
289+
readline_state = ctypes.c_int.in_dll(readline_lib, "rl_readline_state").value
290+
return bool(IN_SEARCH_MODE & readline_state)
291+
elif rl_type == RlType.PYREADLINE:
292+
from pyreadline3.modes.emacs import ( # type: ignore[import]
293+
EmacsMode,
294+
)
295+
296+
# These search modes only apply to Emacs mode, which is the default.
297+
if not isinstance(readline.rl.mode, EmacsMode):
298+
return False
299+
300+
# While in search mode, the current keyevent function is set one of the following.
301+
search_funcs = (
302+
readline.rl.mode._process_incremental_search_keyevent,
303+
readline.rl.mode._process_non_incremental_search_keyevent,
304+
)
305+
return readline.rl.mode.process_keyevent_queue[-1] in search_funcs
306+
else:
307+
return False

0 commit comments

Comments
 (0)