Skip to content

Commit 04df113

Browse files
authored
[interactive] Make minimal mode the default (#2241)
- Don't set GNU readline horizontal-scroll-mode in minimal mode - This addresses issue #2081. - In minimal mode, print completion candidates in columns.
1 parent 8609cd3 commit 04df113

File tree

4 files changed

+61
-60
lines changed

4 files changed

+61
-60
lines changed

core/comp_ui.py

+54-45
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@
2626
PROMPT_UNDERLINE = '\x01%s\x02' % ansi.UNDERLINE
2727
PROMPT_REVERSE = '\x01%s\x02' % ansi.REVERSE
2828

29+
DEFAULT_TERM_WIDTH = 80
30+
DEFAULT_MATCH_LINE_LIMIT = 10
31+
32+
33+
def _GetTerminalWidth():
34+
# type: () -> int
35+
try:
36+
return libc.get_terminal_width()
37+
except (IOError, OSError):
38+
# This shouldn't raise IOError because we did it at startup! Under
39+
# rare circumstances stdin can change, e.g. if you do exec <&
40+
# input.txt. So we have a fallback.
41+
return DEFAULT_TERM_WIDTH
42+
2943

3044
def _PromptLen(prompt_str):
3145
# type: (str) -> int
@@ -85,13 +99,26 @@ def __init__(self):
8599
class _IDisplay(object):
86100
"""Interface for completion displays."""
87101

88-
def __init__(self, comp_state, prompt_state, num_lines_cap, f, debug_f):
89-
# type: (State, PromptState, int, mylib.Writer, _DebugFile) -> None
102+
def __init__(self, comp_state, prompt_state, num_lines_cap, f, debug_f, signal_safe):
103+
# type: (State, PromptState, int, mylib.Writer, _DebugFile, iolib.SignalSafe) -> None
90104
self.comp_state = comp_state
91105
self.prompt_state = prompt_state
92106
self.num_lines_cap = num_lines_cap
93107
self.f = f
94108
self.debug_f = debug_f
109+
self.term_width = _GetTerminalWidth()
110+
self.signal_safe = signal_safe
111+
112+
def _GetTermWidth(self):
113+
# type: () -> int
114+
if self.signal_safe.PollSigWinch(): # is our value dirty?
115+
self.term_width = _GetTerminalWidth()
116+
117+
return self.term_width
118+
119+
def ReadlineInitCommands(self):
120+
# type: () -> List[str]
121+
return []
95122

96123
def PrintCandidates(self, unused_subst, matches, unused_match_len):
97124
# type: (Optional[str], List[str], int) -> None
@@ -142,10 +169,11 @@ class MinimalDisplay(_IDisplay):
142169
without testing it.
143170
"""
144171

145-
def __init__(self, comp_state, prompt_state, debug_f):
146-
# type: (State, PromptState, _DebugFile) -> None
147-
_IDisplay.__init__(self, comp_state, prompt_state, 10, mylib.Stdout(),
148-
debug_f)
172+
def __init__(self, comp_state, prompt_state, debug_f, signal_safe):
173+
# type: (State, PromptState, _DebugFile, iolib.SignalSafe) -> None
174+
_IDisplay.__init__(self, comp_state, prompt_state,
175+
DEFAULT_MATCH_LINE_LIMIT, mylib.Stdout(), debug_f,
176+
signal_safe)
149177

150178
def _RedrawPrompt(self):
151179
# type: () -> None
@@ -161,22 +189,11 @@ def _PrintCandidates(self, unused_subst, matches, unused_match_len):
161189
display_pos = self.comp_state.display_pos
162190
assert display_pos != -1
163191

164-
too_many = False
165-
i = 0
166-
for m in matches:
167-
self.f.write(' %s\n' % m[display_pos:])
168-
169-
if i == self.num_lines_cap:
170-
too_many = True
171-
i += 1 # Count this one
172-
break
173-
174-
i += 1
175-
176-
if too_many:
177-
num_left = len(matches) - i
178-
if num_left:
179-
self.f.write(' ... and %d more\n' % num_left)
192+
to_display = [m[display_pos:] for m in matches]
193+
lens = [len(m) for m in to_display]
194+
max_match_len = max(lens)
195+
term_width = self._GetTermWidth()
196+
_PrintPacked(to_display, max_match_len, term_width, self.num_lines_cap, self.f)
180197

181198
self._RedrawPrompt()
182199

@@ -309,7 +326,6 @@ class NiceDisplay(_IDisplay):
309326

310327
def __init__(
311328
self,
312-
term_width, # type: int
313329
comp_state, # type: State
314330
prompt_state, # type: PromptState
315331
debug_f, # type: _DebugFile
@@ -321,13 +337,11 @@ def __init__(
321337
Args:
322338
bold_line: Should user's entry be bold?
323339
"""
324-
_IDisplay.__init__(self, comp_state, prompt_state, 10, mylib.Stdout(),
325-
debug_f)
326-
327-
self.term_width = term_width # initial terminal width; will be invalidated
340+
_IDisplay.__init__(self, comp_state, prompt_state,
341+
DEFAULT_MATCH_LINE_LIMIT, mylib.Stdout(), debug_f,
342+
signal_safe)
328343

329344
self.readline = readline
330-
self.signal_safe = signal_safe
331345

332346
self.bold_line = False
333347

@@ -340,6 +354,12 @@ def __init__(
340354
# hash of matches -> count. Has exactly ONE entry at a time.
341355
self.dupes = {} # type: Dict[int, int]
342356

357+
def ReadlineInitCommands(self):
358+
# type: () -> List[str]
359+
# NOTE: This setting prevents line-wrapping from clobbering completion
360+
# output. See https://github.com/oils-for-unix/oils/issues/257
361+
return ['set horizontal-scroll-mode on']
362+
343363
def Reset(self):
344364
# type: () -> None
345365
"""Call this in between commands."""
@@ -360,7 +380,7 @@ def _ReturnToPrompt(self, num_lines):
360380

361381
# Go right, but not more than the terminal width.
362382
n = orig_len + last_prompt_len
363-
n = n % self._GetTerminalWidth()
383+
n = n % self._GetTermWidth()
364384
self.f.write('\x1b[%dC' % n) # RIGHT
365385

366386
if self.bold_line:
@@ -370,7 +390,7 @@ def _ReturnToPrompt(self, num_lines):
370390

371391
def _PrintCandidates(self, unused_subst, matches, unused_max_match_len):
372392
# type: (Optional[str], List[str], int) -> None
373-
term_width = self._GetTerminalWidth()
393+
term_width = self._GetTermWidth()
374394

375395
# Variables set by the completion generator. They should always exist,
376396
# because we can't get "matches" without calling that function.
@@ -450,7 +470,7 @@ def PrintRequired(self, msg, *args):
450470
#log('PrintOptional %r', msg, file=DEBUG_F)
451471

452472
# Truncate to terminal width
453-
max_len = self._GetTerminalWidth() - 2
473+
max_len = self._GetTermWidth() - 2
454474
if len(msg) > max_len:
455475
msg = msg[:max_len - 5] + ' ... '
456476

@@ -471,7 +491,7 @@ def PrintOptional(self, msg, *args):
471491

472492
def ShowPromptOnRight(self, rendered):
473493
# type: (str) -> None
474-
n = self._GetTerminalWidth() - 2 - len(rendered)
494+
n = self._GetTermWidth() - 2 - len(rendered)
475495
spaces = ' ' * n
476496

477497
# We avoid drawing problems if we print it on its own line:
@@ -512,18 +532,6 @@ def EraseLines(self):
512532
self.f.write('\x1b[%dA' % n)
513533
self.f.flush() # Without this, output will look messed up
514534

515-
def _GetTerminalWidth(self):
516-
# type: () -> int
517-
if self.signal_safe.PollSigWinch(): # is our value dirty?
518-
try:
519-
self.term_width = libc.get_terminal_width()
520-
except (IOError, OSError):
521-
# This shouldn't raise IOError because we did it at startup! Under
522-
# rare circumstances stdin can change, e.g. if you do exec <&
523-
# input.txt. So we have a fallback.
524-
self.term_width = 80
525-
return self.term_width
526-
527535

528536
def ExecutePrintCandidates(display, sub, matches, max_len):
529537
# type: (_IDisplay, str, List[str], int) -> None
@@ -548,7 +556,8 @@ def InitReadline(
548556

549557
readline.parse_and_bind('tab: complete')
550558

551-
readline.parse_and_bind('set horizontal-scroll-mode on')
559+
for cmd in display.ReadlineInitCommands():
560+
readline.parse_and_bind(cmd)
552561

553562
# How does this map to C?
554563
# https://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC45

core/comp_ui_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ def testDisplays(self):
120120
signal_safe = iolib.InitSignalSafe()
121121

122122
# terminal width
123-
d1 = comp_ui.NiceDisplay(80, comp_ui_state, prompt_state, debug_f,
123+
d1 = comp_ui.NiceDisplay(comp_ui_state, prompt_state, debug_f,
124124
line_input, signal_safe)
125-
d2 = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f)
125+
d2 = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f, signal_safe)
126126

127127
prompt_state.SetLastPrompt('$ ')
128128

core/shell.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -1083,20 +1083,12 @@ def Main(
10831083
mutable_opts.set_redefine_source()
10841084

10851085
if readline:
1086-
term_width = 0
10871086
if flag.completion_display == 'nice':
1088-
try:
1089-
term_width = libc.get_terminal_width()
1090-
except (IOError, OSError): # stdin not a terminal
1091-
pass
1092-
1093-
if term_width != 0:
1094-
display = comp_ui.NiceDisplay(
1095-
term_width, comp_ui_state, prompt_state, debug_f, readline,
1096-
signal_safe) # type: comp_ui._IDisplay
1087+
display = comp_ui.NiceDisplay(comp_ui_state, prompt_state,
1088+
debug_f, readline, signal_safe) # type: comp_ui._IDisplay
10971089
else:
10981090
display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1099-
debug_f)
1091+
debug_f, signal_safe)
11001092

11011093
comp_ui.InitReadline(readline, sh_files.HistoryFile(), root_comp,
11021094
display, debug_f)
@@ -1106,7 +1098,7 @@ def Main(
11061098

11071099
else: # Without readline module
11081100
display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state,
1109-
debug_f)
1101+
debug_f, signal_safe)
11101102

11111103
process.InitInteractiveShell(signal_safe) # Set signal handlers
11121104

frontend/flag_def.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def _AddShellOptions(spec):
272272
default='abbrev-text')
273273

274274
# Defines completion style.
275-
MAIN_SPEC.LongFlag('--completion-display', ['minimal', 'nice'], default='nice')
275+
MAIN_SPEC.LongFlag('--completion-display', ['minimal', 'nice'], default='minimal')
276276
# TODO: Add option for YSH prompt style? RHS prompt?
277277

278278
MAIN_SPEC.LongFlag('--completion-demo')

0 commit comments

Comments
 (0)