| 1 | """comp_ui.py."""
 | 
| 2 | from __future__ import print_function
 | 
| 3 | 
 | 
| 4 | from core import ansi
 | 
| 5 | from core import completion
 | 
| 6 | from data_lang import pretty
 | 
| 7 | import libc
 | 
| 8 | 
 | 
| 9 | from mycpp import mylib
 | 
| 10 | 
 | 
| 11 | from typing import Any, List, Optional, Dict, TYPE_CHECKING
 | 
| 12 | if TYPE_CHECKING:
 | 
| 13 |     from frontend.py_readline import Readline
 | 
| 14 |     from core.util import _DebugFile
 | 
| 15 |     from core import pyos
 | 
| 16 | 
 | 
| 17 | # ANSI escape codes affect the prompt!
 | 
| 18 | # https://superuser.com/questions/301353/escape-non-printing-characters-in-a-function-for-a-bash-prompt
 | 
| 19 | #
 | 
| 20 | # Readline understands \x01 and \x02, while bash understands \[ and \].
 | 
| 21 | 
 | 
| 22 | # NOTE: There were used in demoish.py.  Do we still want those styles?
 | 
| 23 | if 0:
 | 
| 24 |     PROMPT_BOLD = '\x01%s\x02' % ansi.BOLD
 | 
| 25 |     PROMPT_RESET = '\x01%s\x02' % ansi.RESET
 | 
| 26 |     PROMPT_UNDERLINE = '\x01%s\x02' % ansi.UNDERLINE
 | 
| 27 |     PROMPT_REVERSE = '\x01%s\x02' % ansi.REVERSE
 | 
| 28 | 
 | 
| 29 | 
 | 
| 30 | def _PromptLen(prompt_str):
 | 
| 31 |     # type: (str) -> int
 | 
| 32 |     """Ignore all characters between \x01 and \x02 and handle unicode
 | 
| 33 |     characters.
 | 
| 34 | 
 | 
| 35 |     In particular, the display width of a string may be different from
 | 
| 36 |     either the number of bytes or the number of unicode characters.
 | 
| 37 |     Additionally, if there are multiple lines in the prompt, only give
 | 
| 38 |     the length of the last line.
 | 
| 39 |     """
 | 
| 40 |     escaped = False
 | 
| 41 |     display_str = ""
 | 
| 42 |     for c in prompt_str:
 | 
| 43 |         if c == '\x01':
 | 
| 44 |             escaped = True
 | 
| 45 |         elif c == '\x02':
 | 
| 46 |             escaped = False
 | 
| 47 |         elif not escaped:
 | 
| 48 |             # mycpp: rewrite of +=
 | 
| 49 |             display_str = display_str + c
 | 
| 50 |     last_line = display_str.split('\n')[-1]
 | 
| 51 |     return pretty.TryUnicodeWidth(last_line)
 | 
| 52 | 
 | 
| 53 | 
 | 
| 54 | class PromptState(object):
 | 
| 55 |     """For the InteractiveLineReader to communicate with the Display
 | 
| 56 |     callback."""
 | 
| 57 | 
 | 
| 58 |     def __init__(self):
 | 
| 59 |         # type: () -> None
 | 
| 60 |         self.last_prompt_str = None  # type: Optional[str]
 | 
| 61 |         self.last_prompt_len = -1
 | 
| 62 | 
 | 
| 63 |     def SetLastPrompt(self, prompt_str):
 | 
| 64 |         # type: (str) -> None
 | 
| 65 |         self.last_prompt_str = prompt_str
 | 
| 66 |         self.last_prompt_len = _PromptLen(prompt_str)
 | 
| 67 | 
 | 
| 68 | 
 | 
| 69 | class State(object):
 | 
| 70 |     """For the RootCompleter to communicate with the Display callback."""
 | 
| 71 | 
 | 
| 72 |     def __init__(self):
 | 
| 73 |         # type: () -> None
 | 
| 74 |         # original line, truncated
 | 
| 75 |         self.line_until_tab = None  # type: Optional[str]
 | 
| 76 | 
 | 
| 77 |         # Start offset in EVERY candidate to display.  We send fully-completed
 | 
| 78 |         # LINES to readline because we don't want it to do its own word splitting.
 | 
| 79 |         self.display_pos = -1
 | 
| 80 | 
 | 
| 81 |         # completion candidate descriptions
 | 
| 82 |         self.descriptions = {}  # type: Dict[str, str]
 | 
| 83 | 
 | 
| 84 | 
 | 
| 85 | class _IDisplay(object):
 | 
| 86 |     """Interface for completion displays."""
 | 
| 87 | 
 | 
| 88 |     def __init__(self, comp_state, prompt_state, num_lines_cap, f, debug_f):
 | 
| 89 |         # type: (State, PromptState, int, mylib.Writer, _DebugFile) -> None
 | 
| 90 |         self.comp_state = comp_state
 | 
| 91 |         self.prompt_state = prompt_state
 | 
| 92 |         self.num_lines_cap = num_lines_cap
 | 
| 93 |         self.f = f
 | 
| 94 |         self.debug_f = debug_f
 | 
| 95 | 
 | 
| 96 |     def PrintCandidates(self, unused_subst, matches, unused_match_len):
 | 
| 97 |         # type: (Optional[str], List[str], int) -> None
 | 
| 98 |         try:
 | 
| 99 |             self._PrintCandidates(unused_subst, matches, unused_match_len)
 | 
| 100 |         except Exception:
 | 
| 101 |             if 0:
 | 
| 102 |                 import traceback
 | 
| 103 |                 traceback.print_exc()
 | 
| 104 | 
 | 
| 105 |     def _PrintCandidates(self, unused_subst, matches, unused_match_len):
 | 
| 106 |         # type: (Optional[str], List[str], int) -> None
 | 
| 107 |         """Abstract method."""
 | 
| 108 |         raise NotImplementedError()
 | 
| 109 | 
 | 
| 110 |     def Reset(self):
 | 
| 111 |         # type: () -> None
 | 
| 112 |         """Call this in between commands."""
 | 
| 113 |         pass
 | 
| 114 | 
 | 
| 115 |     def ShowPromptOnRight(self, rendered):
 | 
| 116 |         # type: (str) -> None
 | 
| 117 |         # Doesn't apply to MinimalDisplay
 | 
| 118 |         pass
 | 
| 119 | 
 | 
| 120 |     def EraseLines(self):
 | 
| 121 |         # type: () -> None
 | 
| 122 |         # Doesn't apply to MinimalDisplay
 | 
| 123 |         pass
 | 
| 124 | 
 | 
| 125 |     if mylib.PYTHON:
 | 
| 126 | 
 | 
| 127 |         def PrintRequired(self, msg, *args):
 | 
| 128 |             # type: (str, *Any) -> None
 | 
| 129 |             # This gets called with "nothing to display"
 | 
| 130 |             pass
 | 
| 131 | 
 | 
| 132 |         def PrintOptional(self, msg, *args):
 | 
| 133 |             # type: (str, *Any) -> None
 | 
| 134 |             pass
 | 
| 135 | 
 | 
| 136 | 
 | 
| 137 | class MinimalDisplay(_IDisplay):
 | 
| 138 |     """A display with minimal dependencies.
 | 
| 139 | 
 | 
| 140 |     It doesn't output color or depend on the terminal width. It could be
 | 
| 141 |     useful if we ever have a browser build!  We can see completion
 | 
| 142 |     without testing it.
 | 
| 143 |     """
 | 
| 144 | 
 | 
| 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)
 | 
| 149 | 
 | 
| 150 |         self.reader = None
 | 
| 151 | 
 | 
| 152 |     def _RedrawPrompt(self):
 | 
| 153 |         # type: () -> None
 | 
| 154 |         # NOTE: This has to reprint the prompt and the command line!
 | 
| 155 |         # Like bash, we SAVE the prompt and print it, rather than re-evaluating it.
 | 
| 156 |         self.f.write(self.prompt_state.last_prompt_str)
 | 
| 157 |         self.f.write(self.comp_state.line_until_tab)
 | 
| 158 | 
 | 
| 159 |     def _PrintCandidates(self, unused_subst, matches, unused_match_len):
 | 
| 160 |         # type: (Optional[str], List[str], int) -> None
 | 
| 161 |         #log('_PrintCandidates %s', matches)
 | 
| 162 |         self.f.write('\n')  # need this
 | 
| 163 |         display_pos = self.comp_state.display_pos
 | 
| 164 |         assert display_pos != -1
 | 
| 165 | 
 | 
| 166 |         too_many = False
 | 
| 167 |         i = 0
 | 
| 168 |         for m in matches:
 | 
| 169 |             self.f.write(' %s\n' % m[display_pos:])
 | 
| 170 | 
 | 
| 171 |             if i == self.num_lines_cap:
 | 
| 172 |                 too_many = True
 | 
| 173 |                 i += 1  # Count this one
 | 
| 174 |                 break
 | 
| 175 | 
 | 
| 176 |             i += 1
 | 
| 177 | 
 | 
| 178 |         if too_many:
 | 
| 179 |             num_left = len(matches) - i
 | 
| 180 |             if num_left:
 | 
| 181 |                 self.f.write(' ... and %d more\n' % num_left)
 | 
| 182 | 
 | 
| 183 |         self._RedrawPrompt()
 | 
| 184 | 
 | 
| 185 |     if mylib.PYTHON:
 | 
| 186 | 
 | 
| 187 |         def PrintRequired(self, msg, *args):
 | 
| 188 |             # type: (str, *Any) -> None
 | 
| 189 |             self.f.write('\n')
 | 
| 190 |             if args:
 | 
| 191 |                 msg = msg % args
 | 
| 192 |             self.f.write(' %s\n' % msg)  # need a newline
 | 
| 193 |             self._RedrawPrompt()
 | 
| 194 | 
 | 
| 195 | 
 | 
| 196 | def _PrintPacked(matches, max_match_len, term_width, max_lines, f):
 | 
| 197 |     # type: (List[str], int, int, int, mylib.Writer) -> int
 | 
| 198 |     # With of each candidate.  2 spaces between each.
 | 
| 199 |     w = max_match_len + 2
 | 
| 200 | 
 | 
| 201 |     # Number of candidates per line.  Don't print in first or last column.
 | 
| 202 |     num_per_line = max(1, (term_width - 2) // w)
 | 
| 203 | 
 | 
| 204 |     fmt = '%-' + str(w) + 's'
 | 
| 205 |     num_lines = 0
 | 
| 206 | 
 | 
| 207 |     too_many = False
 | 
| 208 |     remainder = num_per_line - 1
 | 
| 209 |     i = 0  # num matches
 | 
| 210 |     for m in matches:
 | 
| 211 |         if i % num_per_line == 0:
 | 
| 212 |             f.write(' ')  # 1 space left gutter
 | 
| 213 | 
 | 
| 214 |         f.write(fmt % m)
 | 
| 215 | 
 | 
| 216 |         if i % num_per_line == remainder:
 | 
| 217 |             f.write('\n')  # newline (leaving 1 space right gutter)
 | 
| 218 |             num_lines += 1
 | 
| 219 | 
 | 
| 220 |             # Check if we've printed enough lines
 | 
| 221 |             if num_lines == max_lines:
 | 
| 222 |                 too_many = True
 | 
| 223 |                 i += 1  # count this one
 | 
| 224 |                 break
 | 
| 225 |         i += 1
 | 
| 226 | 
 | 
| 227 |     # Write last line break, unless it came out exactly.
 | 
| 228 |     if i % num_per_line != 0:
 | 
| 229 |         #log('i = %d, num_per_line = %d, i %% num_per_line = %d',
 | 
| 230 |         #    i, num_per_line, i % num_per_line)
 | 
| 231 | 
 | 
| 232 |         f.write('\n')
 | 
| 233 |         num_lines += 1
 | 
| 234 | 
 | 
| 235 |     if too_many:
 | 
| 236 |         # TODO: Save this in the Display class
 | 
| 237 |         fmt2 = ansi.BOLD + ansi.BLUE + '%' + str(term_width -
 | 
| 238 |                                                  2) + 's' + ansi.RESET
 | 
| 239 |         num_left = len(matches) - i
 | 
| 240 |         if num_left:
 | 
| 241 |             f.write(fmt2 % '... and %d more\n' % num_left)
 | 
| 242 |             num_lines += 1
 | 
| 243 | 
 | 
| 244 |     return num_lines
 | 
| 245 | 
 | 
| 246 | 
 | 
| 247 | def _PrintLong(
 | 
| 248 |         matches,  # type: List[str]
 | 
| 249 |         max_match_len,  # type: int
 | 
| 250 |         term_width,  # type: int
 | 
| 251 |         max_lines,  # type: int
 | 
| 252 |         descriptions,  # type: Dict[str, str]
 | 
| 253 |         f,  # type: mylib.Writer
 | 
| 254 | ):
 | 
| 255 |     # type: (...) -> int
 | 
| 256 |     """Print flags with descriptions, one per line.
 | 
| 257 | 
 | 
| 258 |     Args:
 | 
| 259 |       descriptions: dict of { prefix-stripped match -> description }
 | 
| 260 | 
 | 
| 261 |     Returns:
 | 
| 262 |       The number of lines printed.
 | 
| 263 |     """
 | 
| 264 |     #log('desc = %s', descriptions)
 | 
| 265 | 
 | 
| 266 |     # Subtract 3 chars: 1 for left and right margin, and then 1 for the space in
 | 
| 267 |     # between.
 | 
| 268 |     max_desc = max(0, term_width - max_match_len - 3)
 | 
| 269 |     fmt = ' %-' + str(
 | 
| 270 |         max_match_len) + 's ' + ansi.YELLOW + '%s' + ansi.RESET + '\n'
 | 
| 271 | 
 | 
| 272 |     num_lines = 0
 | 
| 273 | 
 | 
| 274 |     # rl_match is a raw string, which may or may not have a trailing space
 | 
| 275 |     for rl_match in matches:
 | 
| 276 |         desc = descriptions.get(rl_match)
 | 
| 277 |         if desc is None:
 | 
| 278 |             desc = ''
 | 
| 279 |         if max_desc == 0:  # the window is not wide enough for some flag
 | 
| 280 |             f.write(' %s\n' % rl_match)
 | 
| 281 |         else:
 | 
| 282 |             if len(desc) > max_desc:
 | 
| 283 |                 desc = desc[:max_desc - 5] + ' ... '
 | 
| 284 |             f.write(fmt % (rl_match, desc))
 | 
| 285 | 
 | 
| 286 |         num_lines += 1
 | 
| 287 | 
 | 
| 288 |         if num_lines == max_lines:
 | 
| 289 |             # right justify
 | 
| 290 |             fmt2 = ansi.BOLD + ansi.BLUE + '%' + str(term_width -
 | 
| 291 |                                                      1) + 's' + ansi.RESET
 | 
| 292 |             num_left = len(matches) - num_lines
 | 
| 293 |             if num_left:
 | 
| 294 |                 f.write(fmt2 % '... and %d more\n' % num_left)
 | 
| 295 |                 num_lines += 1
 | 
| 296 |             break
 | 
| 297 | 
 | 
| 298 |     return num_lines
 | 
| 299 | 
 | 
| 300 | 
 | 
| 301 | class NiceDisplay(_IDisplay):
 | 
| 302 |     """Methods to display completion candidates and other messages.
 | 
| 303 | 
 | 
| 304 |     This object has to remember how many lines we last drew, in order to erase
 | 
| 305 |     them before drawing something new.
 | 
| 306 | 
 | 
| 307 |     It's also useful for:
 | 
| 308 |     - Stripping off the common prefix according to OUR rules, not readline's.
 | 
| 309 |     - displaying descriptions of flags and builtins
 | 
| 310 |     """
 | 
| 311 | 
 | 
| 312 |     def __init__(
 | 
| 313 |             self,
 | 
| 314 |             term_width,  # type: int
 | 
| 315 |             comp_state,  # type: State
 | 
| 316 |             prompt_state,  # type: PromptState
 | 
| 317 |             debug_f,  # type: _DebugFile
 | 
| 318 |             readline,  # type: Optional[Readline]
 | 
| 319 |             signal_safe,  # type: pyos.SignalSafe
 | 
| 320 |     ):
 | 
| 321 |         # type: (...) -> None
 | 
| 322 |         """
 | 
| 323 |     Args:
 | 
| 324 |       bold_line: Should user's entry be bold?
 | 
| 325 |     """
 | 
| 326 |         _IDisplay.__init__(self, comp_state, prompt_state, 10, mylib.Stdout(),
 | 
| 327 |                            debug_f)
 | 
| 328 | 
 | 
| 329 |         self.term_width = term_width  # initial terminal width; will be invalidated
 | 
| 330 | 
 | 
| 331 |         self.readline = readline
 | 
| 332 |         self.signal_safe = signal_safe
 | 
| 333 | 
 | 
| 334 |         self.bold_line = False
 | 
| 335 | 
 | 
| 336 |         self.num_lines_last_displayed = 0
 | 
| 337 | 
 | 
| 338 |         # For debugging only, could get rid of
 | 
| 339 |         self.c_count = 0
 | 
| 340 |         self.m_count = 0
 | 
| 341 | 
 | 
| 342 |         # hash of matches -> count.  Has exactly ONE entry at a time.
 | 
| 343 |         self.dupes = {}  # type: Dict[int, int]
 | 
| 344 | 
 | 
| 345 |     def Reset(self):
 | 
| 346 |         # type: () -> None
 | 
| 347 |         """Call this in between commands."""
 | 
| 348 |         self.num_lines_last_displayed = 0
 | 
| 349 |         self.dupes.clear()
 | 
| 350 | 
 | 
| 351 |     def _ReturnToPrompt(self, num_lines):
 | 
| 352 |         # type: (int) -> None
 | 
| 353 |         # NOTE: We can't use ANSI terminal codes to save and restore the prompt,
 | 
| 354 |         # because the screen may have scrolled.  Instead we have to keep track of
 | 
| 355 |         # how many lines we printed and the original column of the cursor.
 | 
| 356 | 
 | 
| 357 |         orig_len = len(self.comp_state.line_until_tab)
 | 
| 358 | 
 | 
| 359 |         self.f.write('\x1b[%dA' % num_lines)  # UP
 | 
| 360 |         last_prompt_len = self.prompt_state.last_prompt_len
 | 
| 361 |         assert last_prompt_len != -1
 | 
| 362 | 
 | 
| 363 |         # Go right, but not more than the terminal width.
 | 
| 364 |         n = orig_len + last_prompt_len
 | 
| 365 |         n = n % self._GetTerminalWidth()
 | 
| 366 |         self.f.write('\x1b[%dC' % n)  # RIGHT
 | 
| 367 | 
 | 
| 368 |         if self.bold_line:
 | 
| 369 |             self.f.write(ansi.BOLD)  # Experiment
 | 
| 370 | 
 | 
| 371 |         self.f.flush()
 | 
| 372 | 
 | 
| 373 |     def _PrintCandidates(self, unused_subst, matches, unused_max_match_len):
 | 
| 374 |         # type: (Optional[str], List[str], int) -> None
 | 
| 375 |         term_width = self._GetTerminalWidth()
 | 
| 376 | 
 | 
| 377 |         # Variables set by the completion generator.  They should always exist,
 | 
| 378 |         # because we can't get "matches" without calling that function.
 | 
| 379 |         display_pos = self.comp_state.display_pos
 | 
| 380 |         self.debug_f.write('DISPLAY POS in _PrintCandidates = %d\n' %
 | 
| 381 |                            display_pos)
 | 
| 382 | 
 | 
| 383 |         self.f.write('\n')
 | 
| 384 | 
 | 
| 385 |         self.EraseLines()  # Delete previous completions!
 | 
| 386 |         #log('_PrintCandidates %r', unused_subst, file=DEBUG_F)
 | 
| 387 | 
 | 
| 388 |         # Figure out if the user hit TAB multiple times to show more matches.
 | 
| 389 |         # It's not correct to hash the line itself, because two different lines can
 | 
| 390 |         # have the same completions:
 | 
| 391 |         #
 | 
| 392 |         # ls <TAB>
 | 
| 393 |         # ls --<TAB>
 | 
| 394 |         #
 | 
| 395 |         # This is because there is a common prefix.
 | 
| 396 |         # So instead use the hash of all matches as the identity.
 | 
| 397 | 
 | 
| 398 |         # This could be more accurate but I think it's good enough.
 | 
| 399 |         comp_id = hash(''.join(matches))
 | 
| 400 |         if comp_id in self.dupes:
 | 
| 401 |             # mycpp: rewrite of +=
 | 
| 402 |             self.dupes[comp_id] = self.dupes[comp_id] + 1
 | 
| 403 |         else:
 | 
| 404 |             self.dupes.clear()  # delete the old ones
 | 
| 405 |             self.dupes[comp_id] = 1
 | 
| 406 | 
 | 
| 407 |         max_lines = self.num_lines_cap * self.dupes[comp_id]
 | 
| 408 | 
 | 
| 409 |         assert display_pos != -1
 | 
| 410 |         if display_pos == 0:  # slight optimization for first word
 | 
| 411 |             to_display = matches
 | 
| 412 |         else:
 | 
| 413 |             to_display = [m[display_pos:] for m in matches]
 | 
| 414 | 
 | 
| 415 |         # Calculate max length after stripping prefix.
 | 
| 416 |         lens = [len(m) for m in to_display]
 | 
| 417 |         max_match_len = max(lens)
 | 
| 418 | 
 | 
| 419 |         # TODO: NiceDisplay should truncate when max_match_len > term_width?
 | 
| 420 |         # Also truncate when a single candidate is super long?
 | 
| 421 | 
 | 
| 422 |         # Print and go back up.  But we have to ERASE these before hitting enter!
 | 
| 423 |         if self.comp_state.descriptions is not None and len(
 | 
| 424 |                 self.comp_state.descriptions) > 0:  # exists and is NON EMPTY
 | 
| 425 |             num_lines = _PrintLong(to_display, max_match_len, term_width,
 | 
| 426 |                                    max_lines, self.comp_state.descriptions,
 | 
| 427 |                                    self.f)
 | 
| 428 |         else:
 | 
| 429 |             num_lines = _PrintPacked(to_display, max_match_len, term_width,
 | 
| 430 |                                      max_lines, self.f)
 | 
| 431 | 
 | 
| 432 |         self._ReturnToPrompt(num_lines + 1)
 | 
| 433 |         self.num_lines_last_displayed = num_lines
 | 
| 434 | 
 | 
| 435 |         self.c_count += 1
 | 
| 436 | 
 | 
| 437 |     if mylib.PYTHON:
 | 
| 438 | 
 | 
| 439 |         def PrintRequired(self, msg, *args):
 | 
| 440 |             # type: (str, *Any) -> None
 | 
| 441 |             """Print a message below the prompt, and then return to the
 | 
| 442 |             location on the prompt line."""
 | 
| 443 |             if args:
 | 
| 444 |                 msg = msg % args
 | 
| 445 | 
 | 
| 446 |             # This will mess up formatting
 | 
| 447 |             assert not msg.endswith('\n'), msg
 | 
| 448 | 
 | 
| 449 |             self.f.write('\n')
 | 
| 450 | 
 | 
| 451 |             self.EraseLines()
 | 
| 452 |             #log('PrintOptional %r', msg, file=DEBUG_F)
 | 
| 453 | 
 | 
| 454 |             # Truncate to terminal width
 | 
| 455 |             max_len = self._GetTerminalWidth() - 2
 | 
| 456 |             if len(msg) > max_len:
 | 
| 457 |                 msg = msg[:max_len - 5] + ' ... '
 | 
| 458 | 
 | 
| 459 |             # NOTE: \n at end is REQUIRED.  Otherwise we get drawing problems when on
 | 
| 460 |             # the last line.
 | 
| 461 |             fmt = ansi.BOLD + ansi.BLUE + '%' + str(
 | 
| 462 |                 max_len) + 's' + ansi.RESET + '\n'
 | 
| 463 |             self.f.write(fmt % msg)
 | 
| 464 | 
 | 
| 465 |             self._ReturnToPrompt(2)
 | 
| 466 | 
 | 
| 467 |             self.num_lines_last_displayed = 1
 | 
| 468 |             self.m_count += 1
 | 
| 469 | 
 | 
| 470 |         def PrintOptional(self, msg, *args):
 | 
| 471 |             # type: (str, *Any) -> None
 | 
| 472 |             self.PrintRequired(msg, *args)
 | 
| 473 | 
 | 
| 474 |     def ShowPromptOnRight(self, rendered):
 | 
| 475 |         # type: (str) -> None
 | 
| 476 |         n = self._GetTerminalWidth() - 2 - len(rendered)
 | 
| 477 |         spaces = ' ' * n
 | 
| 478 | 
 | 
| 479 |         # We avoid drawing problems if we print it on its own line:
 | 
| 480 |         # - inserting text doesn't push it to the right
 | 
| 481 |         # - you can't overwrite it
 | 
| 482 |         self.f.write(spaces + ansi.REVERSE + ' ' + rendered + ' ' +
 | 
| 483 |                      ansi.RESET + '\r\n')
 | 
| 484 | 
 | 
| 485 |     def EraseLines(self):
 | 
| 486 |         # type: () -> None
 | 
| 487 |         """Clear N lines one-by-one.
 | 
| 488 | 
 | 
| 489 |         Assume the cursor is right below thep rompt:
 | 
| 490 | 
 | 
| 491 |         ish$ echo hi
 | 
| 492 |         _ <-- HERE
 | 
| 493 | 
 | 
| 494 |         That's the first line to erase out of N.  After erasing them, return it
 | 
| 495 |         there.
 | 
| 496 |         """
 | 
| 497 |         if self.bold_line:
 | 
| 498 |             self.f.write(ansi.RESET)  # if command is bold
 | 
| 499 |             self.f.flush()
 | 
| 500 | 
 | 
| 501 |         n = self.num_lines_last_displayed
 | 
| 502 | 
 | 
| 503 |         #log('EraseLines %d (c = %d, m = %d)', n, self.c_count, self.m_count,
 | 
| 504 |         #    file=DEBUG_F)
 | 
| 505 | 
 | 
| 506 |         if n == 0:
 | 
| 507 |             return
 | 
| 508 | 
 | 
| 509 |         for i in xrange(n):
 | 
| 510 |             self.f.write('\x1b[2K')  # 2K clears entire line (not 0K or 1K)
 | 
| 511 |             self.f.write('\x1b[1B')  # go down one line
 | 
| 512 | 
 | 
| 513 |         # Now go back up
 | 
| 514 |         self.f.write('\x1b[%dA' % n)
 | 
| 515 |         self.f.flush()  # Without this, output will look messed up
 | 
| 516 | 
 | 
| 517 |     def _GetTerminalWidth(self):
 | 
| 518 |         # type: () -> int
 | 
| 519 |         if self.signal_safe.PollSigWinch():  # is our value dirty?
 | 
| 520 |             try:
 | 
| 521 |                 self.term_width = libc.get_terminal_width()
 | 
| 522 |             except (IOError, OSError):
 | 
| 523 |                 # This shouldn't raise IOError because we did it at startup!  Under
 | 
| 524 |                 # rare circumstances stdin can change, e.g. if you do exec <&
 | 
| 525 |                 # input.txt.  So we have a fallback.
 | 
| 526 |                 self.term_width = 80
 | 
| 527 |         return self.term_width
 | 
| 528 | 
 | 
| 529 | 
 | 
| 530 | def ExecutePrintCandidates(display, sub, matches, max_len):
 | 
| 531 |     # type: (_IDisplay, str, List[str], int) -> None
 | 
| 532 |     display.PrintCandidates(sub, matches, max_len)
 | 
| 533 | 
 | 
| 534 | 
 | 
| 535 | def InitReadline(
 | 
| 536 |         readline,  # type: Optional[Readline]
 | 
| 537 |         hist_file,  # type: Optional[str]
 | 
| 538 |         root_comp,  # type: completion.RootCompleter
 | 
| 539 |         display,  # type: _IDisplay
 | 
| 540 |         debug_f,  # type: _DebugFile
 | 
| 541 | ):
 | 
| 542 |     # type: (...) -> None
 | 
| 543 |     assert readline
 | 
| 544 | 
 | 
| 545 |     if hist_file is not None:
 | 
| 546 |         try:
 | 
| 547 |             readline.read_history_file(hist_file)
 | 
| 548 |         except (IOError, OSError):
 | 
| 549 |             pass
 | 
| 550 | 
 | 
| 551 |     readline.parse_and_bind('tab: complete')
 | 
| 552 | 
 | 
| 553 |     readline.parse_and_bind('set horizontal-scroll-mode on')
 | 
| 554 | 
 | 
| 555 |     # How does this map to C?
 | 
| 556 |     # https://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC45
 | 
| 557 | 
 | 
| 558 |     complete_cb = completion.ReadlineCallback(readline, root_comp, debug_f)
 | 
| 559 |     readline.set_completer(complete_cb)
 | 
| 560 | 
 | 
| 561 |     # http://web.mit.edu/gnu/doc/html/rlman_2.html#SEC39
 | 
| 562 |     # "The basic list of characters that signal a break between words for the
 | 
| 563 |     # completer routine. The default value of this variable is the characters
 | 
| 564 |     # which break words for completion in Bash, i.e., " \t\n\"\\'`@$><=;|&{(""
 | 
| 565 | 
 | 
| 566 |     # This determines the boundaries you get back from get_begidx() and
 | 
| 567 |     # get_endidx() at completion time!
 | 
| 568 |     # We could be more conservative and set it to ' ', but then cases like
 | 
| 569 |     # 'ls|w<TAB>' would try to complete the whole thing, instead of just 'w'.
 | 
| 570 |     #
 | 
| 571 |     # Note that this should not affect the OSH completion algorithm.  It only
 | 
| 572 |     # affects what we pass back to readline and what readline displays to the
 | 
| 573 |     # user!
 | 
| 574 | 
 | 
| 575 |     # No delimiters because readline isn't smart enough to tokenize shell!
 | 
| 576 |     readline.set_completer_delims('')
 | 
| 577 | 
 | 
| 578 |     readline.set_completion_display_matches_hook(display)
 |