1# Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com> 2# Alex Gaynor 3# Antonio Cuni 4# Armin Rigo 5# Holger Krekel 6# 7# All Rights Reserved 8# 9# 10# Permission to use, copy, modify, and distribute this software and 11# its documentation for any purpose is hereby granted without fee, 12# provided that the above copyright notice appear in all copies and 13# that both that copyright notice and this permission notice appear in 14# supporting documentation. 15# 16# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO 17# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 18# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 19# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 20# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 21# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 22# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 24"""A compatibility wrapper reimplementing the 'readline' standard module 25on top of pyrepl. Not all functionalities are supported. Contains 26extensions for multiline input. 27""" 28 29import sys 30import os 31from pyrepl import commands 32from pyrepl.historical_reader import HistoricalReader 33from pyrepl.completing_reader import CompletingReader 34from pyrepl.unix_console import UnixConsole, _error 35 36 37ENCODING = sys.getfilesystemencoding() or 'latin1' # XXX review 38 39__all__ = [ 40 'add_history', 41 'clear_history', 42 'get_begidx', 43 'get_completer', 44 'get_completer_delims', 45 'get_current_history_length', 46 'get_endidx', 47 'get_history_item', 48 'get_history_length', 49 'get_line_buffer', 50 'insert_text', 51 'parse_and_bind', 52 'read_history_file', 53 'read_init_file', 54 'redisplay', 55 'remove_history_item', 56 'replace_history_item', 57 'set_completer', 58 'set_completer_delims', 59 'set_history_length', 60 'set_pre_input_hook', 61 'set_startup_hook', 62 'write_history_file', 63 # ---- multiline extensions ---- 64 'multiline_input', 65] 66 67# ____________________________________________________________ 68 69 70class ReadlineConfig(object): 71 readline_completer = None 72 completer_delims = dict.fromkeys(' \t\n`~!@#$%^&*()-=+[{]}\\|;:\'",<>/?') 73 74 75class ReadlineAlikeReader(HistoricalReader, CompletingReader): 76 assume_immutable_completions = False 77 use_brackets = False 78 sort_in_column = True 79 80 def error(self, msg="none"): 81 pass # don't show error messages by default 82 83 def get_stem(self): 84 b = self.buffer 85 p = self.pos - 1 86 completer_delims = self.config.completer_delims 87 while p >= 0 and b[p] not in completer_delims: 88 p -= 1 89 return ''.join(b[p+1:self.pos]) 90 91 def get_completions(self, stem): 92 result = [] 93 function = self.config.readline_completer 94 if function is not None: 95 try: 96 stem = str(stem) # rlcompleter.py seems to not like unicode 97 except UnicodeEncodeError: 98 pass # but feed unicode anyway if we have no choice 99 state = 0 100 while True: 101 try: 102 next = function(stem, state) 103 except: 104 break 105 if not isinstance(next, str): 106 break 107 result.append(next) 108 state += 1 109 # emulate the behavior of the standard readline that sorts 110 # the completions before displaying them. 111 result.sort() 112 return result 113 114 def get_trimmed_history(self, maxlength): 115 if maxlength >= 0: 116 cut = len(self.history) - maxlength 117 if cut < 0: 118 cut = 0 119 else: 120 cut = 0 121 return self.history[cut:] 122 123 # --- simplified support for reading multiline Python statements --- 124 125 # This duplicates small parts of pyrepl.python_reader. I'm not 126 # reusing the PythonicReader class directly for two reasons. One is 127 # to try to keep as close as possible to CPython's prompt. The 128 # other is that it is the readline module that we are ultimately 129 # implementing here, and I don't want the built-in raw_input() to 130 # start trying to read multiline inputs just because what the user 131 # typed look like valid but incomplete Python code. So we get the 132 # multiline feature only when using the multiline_input() function 133 # directly (see _pypy_interact.py). 134 135 more_lines = None 136 137 def collect_keymap(self): 138 return super(ReadlineAlikeReader, self).collect_keymap() + ( 139 (r'\n', 'maybe-accept'),) 140 141 def __init__(self, console): 142 super(ReadlineAlikeReader, self).__init__(console) 143 self.commands['maybe_accept'] = maybe_accept 144 self.commands['maybe-accept'] = maybe_accept 145 146 def after_command(self, cmd): 147 super(ReadlineAlikeReader, self).after_command(cmd) 148 if self.more_lines is None: 149 # Force single-line input if we are in raw_input() mode. 150 # Although there is no direct way to add a \n in this mode, 151 # multiline buffers can still show up using various 152 # commands, e.g. navigating the history. 153 try: 154 index = self.buffer.index("\n") 155 except ValueError: 156 pass 157 else: 158 self.buffer = self.buffer[:index] 159 if self.pos > len(self.buffer): 160 self.pos = len(self.buffer) 161 162 163class maybe_accept(commands.Command): 164 def do(self): 165 r = self.reader 166 r.dirty = 1 # this is needed to hide the completion menu, if visible 167 # 168 # if there are already several lines and the cursor 169 # is not on the last one, always insert a new \n. 170 text = r.get_unicode() 171 if "\n" in r.buffer[r.pos:]: 172 r.insert("\n") 173 elif r.more_lines is not None and r.more_lines(text): 174 r.insert("\n") 175 else: 176 self.finish = 1 177 178 179class _ReadlineWrapper(object): 180 reader = None 181 saved_history_length = -1 182 startup_hook = None 183 config = ReadlineConfig() 184 stdin = None 185 stdout = None 186 stderr = None 187 188 def __init__(self, f_in=None, f_out=None): 189 self.f_in = f_in if f_in is not None else os.dup(0) 190 self.f_out = f_out if f_out is not None else os.dup(1) 191 192 def setup_std_streams(self, stdin, stdout, stderr): 193 self.stdin = stdin 194 self.stdout = stdout 195 self.stderr = stderr 196 197 def get_reader(self): 198 if self.reader is None: 199 console = UnixConsole(self.f_in, self.f_out, encoding=ENCODING) 200 self.reader = ReadlineAlikeReader(console) 201 self.reader.config = self.config 202 return self.reader 203 204 def raw_input(self, prompt=''): 205 try: 206 reader = self.get_reader() 207 except _error: 208 return _old_raw_input(prompt) 209 210 # the builtin raw_input calls PyOS_StdioReadline, which flushes 211 # stdout/stderr before displaying the prompt. Try to mimic this 212 # behavior: it seems to be the correct thing to do, and moreover it 213 # mitigates this pytest issue: 214 # https://github.com/pytest-dev/pytest/issues/5134 215 if self.stdout and hasattr(self.stdout, 'flush'): 216 self.stdout.flush() 217 if self.stderr and hasattr(self.stderr, 'flush'): 218 self.stderr.flush() 219 220 reader.ps1 = prompt 221 return reader.readline(startup_hook=self.startup_hook) 222 223 def multiline_input(self, more_lines, ps1, ps2, returns_unicode=False): 224 """Read an input on possibly multiple lines, asking for more 225 lines as long as 'more_lines(unicodetext)' returns an object whose 226 boolean value is true. 227 """ 228 reader = self.get_reader() 229 saved = reader.more_lines 230 try: 231 reader.more_lines = more_lines 232 reader.ps1 = reader.ps2 = ps1 233 reader.ps3 = reader.ps4 = ps2 234 return reader.readline(returns_unicode=returns_unicode) 235 finally: 236 reader.more_lines = saved 237 238 def parse_and_bind(self, string): 239 pass # XXX we don't support parsing GNU-readline-style init files 240 241 def set_completer(self, function=None): 242 self.config.readline_completer = function 243 244 def get_completer(self): 245 return self.config.readline_completer 246 247 def set_completer_delims(self, string): 248 self.config.completer_delims = dict.fromkeys(string) 249 250 def get_completer_delims(self): 251 chars = self.config.completer_delims.keys() 252 chars.sort() 253 return ''.join(chars) 254 255 def _histline(self, line): 256 line = line.rstrip('\n') 257 try: 258 return unicode(line, ENCODING) 259 except UnicodeDecodeError: # bah, silently fall back... 260 return unicode(line, 'utf-8', 'replace') 261 262 def get_history_length(self): 263 return self.saved_history_length 264 265 def set_history_length(self, length): 266 self.saved_history_length = length 267 268 def get_current_history_length(self): 269 return len(self.get_reader().history) 270 271 def read_history_file(self, filename='~/.history'): 272 # multiline extension (really a hack) for the end of lines that 273 # are actually continuations inside a single multiline_input() 274 # history item: we use \r\n instead of just \n. If the history 275 # file is passed to GNU readline, the extra \r are just ignored. 276 history = self.get_reader().history 277 f = open(os.path.expanduser(filename), 'r') 278 buffer = [] 279 for line in f: 280 if line.endswith('\r\n'): 281 buffer.append(line) 282 else: 283 line = self._histline(line) 284 if buffer: 285 line = ''.join(buffer).replace('\r', '') + line 286 del buffer[:] 287 if line: 288 history.append(line) 289 f.close() 290 291 def write_history_file(self, filename='~/.history'): 292 maxlength = self.saved_history_length 293 history = self.get_reader().get_trimmed_history(maxlength) 294 f = open(os.path.expanduser(filename), 'w') 295 for entry in history: 296 if isinstance(entry, unicode): 297 try: 298 entry = entry.encode(ENCODING) 299 except UnicodeEncodeError: # bah, silently fall back... 300 entry = entry.encode('utf-8') 301 entry = entry.replace('\n', '\r\n') # multiline history support 302 f.write(entry + '\n') 303 f.close() 304 305 def clear_history(self): 306 del self.get_reader().history[:] 307 308 def get_history_item(self, index): 309 history = self.get_reader().history 310 if 1 <= index <= len(history): 311 return history[index-1] 312 else: 313 return None # blame readline.c for not raising 314 315 def remove_history_item(self, index): 316 history = self.get_reader().history 317 if 0 <= index < len(history): 318 del history[index] 319 else: 320 raise ValueError("No history item at position %d" % index) 321 # blame readline.c for raising ValueError 322 323 def replace_history_item(self, index, line): 324 history = self.get_reader().history 325 if 0 <= index < len(history): 326 history[index] = self._histline(line) 327 else: 328 raise ValueError("No history item at position %d" % index) 329 # blame readline.c for raising ValueError 330 331 def add_history(self, line): 332 self.get_reader().history.append(self._histline(line)) 333 334 def set_startup_hook(self, function=None): 335 self.startup_hook = function 336 337 def get_line_buffer(self): 338 return self.get_reader().get_buffer() 339 340 def _get_idxs(self): 341 start = cursor = self.get_reader().pos 342 buf = self.get_line_buffer() 343 for i in xrange(cursor - 1, -1, -1): 344 if buf[i] in self.get_completer_delims(): 345 break 346 start = i 347 return start, cursor 348 349 def get_begidx(self): 350 return self._get_idxs()[0] 351 352 def get_endidx(self): 353 return self._get_idxs()[1] 354 355 def insert_text(self, text): 356 return self.get_reader().insert(text) 357 358 359_wrapper = _ReadlineWrapper() 360 361# ____________________________________________________________ 362# Public API 363 364parse_and_bind = _wrapper.parse_and_bind 365set_completer = _wrapper.set_completer 366get_completer = _wrapper.get_completer 367set_completer_delims = _wrapper.set_completer_delims 368get_completer_delims = _wrapper.get_completer_delims 369get_history_length = _wrapper.get_history_length 370set_history_length = _wrapper.set_history_length 371get_current_history_length = _wrapper.get_current_history_length 372read_history_file = _wrapper.read_history_file 373write_history_file = _wrapper.write_history_file 374clear_history = _wrapper.clear_history 375get_history_item = _wrapper.get_history_item 376remove_history_item = _wrapper.remove_history_item 377replace_history_item = _wrapper.replace_history_item 378add_history = _wrapper.add_history 379set_startup_hook = _wrapper.set_startup_hook 380get_line_buffer = _wrapper.get_line_buffer 381get_begidx = _wrapper.get_begidx 382get_endidx = _wrapper.get_endidx 383insert_text = _wrapper.insert_text 384 385# Extension 386multiline_input = _wrapper.multiline_input 387 388# Internal hook 389_get_reader = _wrapper.get_reader 390 391# ____________________________________________________________ 392# Stubs 393 394 395def _make_stub(_name, _ret): 396 def stub(*args, **kwds): 397 import warnings 398 warnings.warn("readline.%s() not implemented" % _name, stacklevel=2) 399 stub.func_name = _name 400 globals()[_name] = stub 401 402for _name, _ret in [ 403 ('read_init_file', None), 404 ('redisplay', None), 405 ('set_pre_input_hook', None), 406]: 407 assert _name not in globals(), _name 408 _make_stub(_name, _ret) 409 410 411def _setup(): 412 global _old_raw_input 413 if _old_raw_input is not None: 414 return 415 # don't run _setup twice 416 417 try: 418 f_in = sys.stdin.fileno() 419 f_out = sys.stdout.fileno() 420 except (AttributeError, ValueError): 421 return 422 if not os.isatty(f_in) or not os.isatty(f_out): 423 return 424 425 _wrapper.f_in = f_in 426 _wrapper.f_out = f_out 427 _wrapper.setup_std_streams(sys.stdin, sys.stdout, sys.stderr) 428 429 if '__pypy__' in sys.builtin_module_names: # PyPy 430 431 def _old_raw_input(prompt=''): 432 # sys.__raw_input__() is only called when stdin and stdout are 433 # as expected and are ttys. If it is the case, then get_reader() 434 # should not really fail in _wrapper.raw_input(). If it still 435 # does, then we will just cancel the redirection and call again 436 # the built-in raw_input(). 437 try: 438 del sys.__raw_input__ 439 except AttributeError: 440 pass 441 return raw_input(prompt) 442 sys.__raw_input__ = _wrapper.raw_input 443 444 else: 445 # this is not really what readline.c does. Better than nothing I guess 446 import __builtin__ 447 _old_raw_input = __builtin__.raw_input 448 __builtin__.raw_input = _wrapper.raw_input 449 450_old_raw_input = None 451_setup() 452